views:

97

answers:

2

I just ran across an issue that probably exposes my ignorance of common threading semantics. I assumed that the following ruby was valid:

   d = Thread.new{ Thread.stop }
   d.join

That is, I thought that joining a stopped thread would simply be a NOP - the thread is already finished, so join should return immediately.

Instead, Ruby-1.8.6 (irb) on Mac returns the following:

deadlock 0x569f14: sleep:-  - (irb):1
deadlock 0x35700: sleep:J(0x569f14) (main) - (irb):2
fatal: Thread(0x35700): deadlock
    from (irb):2:in `join'
    from (irb):2

Can anyone briefly explain why this is so? Is this just an implementation issue or a higher-level semantic that has gone over my head?

+1  A: 

Your code creates a new Thread object with the corresponding block but never runs it, so when you do the join, you're waiting for a thread to complete that is never started. Start the thread before you do the join and it should be fine:

d = Thread.new{ Thread.stop }
d.run
d.join
Chris Bunch
+1  A: 

Thread.stop doesn't end a thread, it merely pauses it. To end a thread, either return from the block, or call Thread.exit:

#!/usr/bin/ruby1.8

t = Thread.new do
  Thread.exit
end
t.join

In the snippet from your question:

d = Thread.new{ Thread.stop }
d.join

the thread runs automatically; no call to Thread.run is necessary for the thread to start. However, the thread stops itself, which is not the same as the thread exiting: the thread needs only a call to Thread#run to start it running again. When the main thread calls Thread#join, the main thread stops being runnable. The interpreter looks for another thread to run, but the only other thread isn't runnable either. Ruby declares defeat and tells you, in a roundabout way, that it doesn't have any executable threads.

This code does not exhibit the deadlock most of the time, but contains a race condition:

d = Thread.new{ Thread.stop }
d.run
d.join

When it works, it's because Thread.stop happened before Thread#run. However, if Thread#run happens first, then Thread.stop will be waiting forever for a Thread#run that will never again happen. The result, once again, is that no threads are available to run:

t = Thread.new do
  10000000.times {}
  Thread.stop
end
t.run
t.join

 # > deadlock 0xb7b47320: sleep:-  - /tmp/foo.rb:5
 # > deadlock 0xb7dcb1b4: sleep:J(0xb7b47320) (main) - /tmp/foo.rb:8
 # > /tmp/foo.rb:8:in `join': Thread(0xb7dcb1b4): deadlock (fatal)
 # >         from /tmp/foo.rb:8
Wayne Conrad