0

I need to write a Ruby program that executes a background program, and performs some functionality on it.

Before doing any operation, the main thread needs to make sure that the background thread is started. What the correct pattern?

This is not exact:

condition = ConditionVariable.new
mutex = Mutex.new

thread = Thread.new do
  mutex.synchronize  { condition.signal }
  # background work
end

mutex.synchronize { condition.wait(mutex) }
# other work

because :signal could execute before :wait, blocking the main thread.

An exact solution is:

thread = Thread.new do
  Thread.current[:started] = true
  # background work
end

sleep 0.01 while thread[:started].nil?
# other work

however, it uses sleep, which I'd like to avoid.

Another exact, but more complex, solution is:

mutex = Mutex.new
condition = ConditionVariable.new

thread = Thread.new do
  mutex.synchronize do
    Thread.current[:started] = true
    condition.signal
  end
  # background work
end

mutex.synchronize do
  condition.wait(mutex) if !thread[:started]
end

# other work

Is there any exact, simple and idiomatic way to structure this functionality?

Marcus
  • 5,104
  • 2
  • 28
  • 24
  • 1
    You could _not_ use `sleep` and just wait in a loop as well. I don't think there's anything wrong with waiting in a loop. – Josh Voigts Mar 08 '18 at 19:51
  • @JoshVoigts If I understood well, I thought of the same salution, however is more complex, as it requires a `Mutex` and a `ConditionVariable`, in order to get a `wait()` mechanism. I'll add the code to the question. Thanks! – Marcus Mar 08 '18 at 20:13
  • Your last example is pretty much the way to do this type of thing, although I would use an explicit variable rather than a thread local, and wrap the `wait` in a `while`, not just an `if`. – matt Mar 08 '18 at 20:42
  • 1
    @JoshVoigts I am not so sure of this. Try it yourself, open a irb and run `until false; end` - find the PID, and look it up in your process monitor ... for me it took nearly 100% cpu. – max pleaner Mar 08 '18 at 20:50
  • @JoshVoigts when I change it to `until false; sleep 1; end`, the CPU consumption goes down to nearly 0, so the sleep is definitely a good thing there. – max pleaner Mar 08 '18 at 20:51
  • @matt thanks. is there a specific reason to use the `while()` construct? the flow control has only two cases : (true -> false), when the thread is not started), and (false) when the thread is started, therefore an `if` construct would be the same, without an underlying implication of a cycle. Is it perhaps that `while` plus `wait()` is a typical pattern? – Marcus Mar 08 '18 at 21:12
  • 1
    @Marcus The reason is that in general it’s possible for the condition that is being waited on to no longer hold by the time the notified thread re-acquires the lock and continues, e.g. a third thread has swooped in and claimed the resource in the time between the notification being sent and the notified thread continuing. In the fairly simple case in the question that’s not going to happen, but also see https://en.wikipedia.org/wiki/Spurious_wakeup – it’s not guaranteed that the thread was woken by another thread notifying it, so you should use `while` anyway. – matt Mar 08 '18 at 22:43
  • @matt wow, that's very interesting! thanks. – Marcus Mar 10 '18 at 09:37

1 Answers1

1

You could use a Queue:

queue = Queue.new

thread = Thread.new do
  queue.push :ready
  # background work
end

queue.pop
# other work

Queue#pop will wait until an item is available and return it.

Stefan
  • 109,145
  • 14
  • 143
  • 218