On Wed, 04 Oct 2000 07:05:22 +0900, Aleksi Niemelwrote: > How one should be using condition variables in concurrent programming? Few > examples that I don't understand what's going on, and probably highlight the > lack of concurrent programming knowledge of the author of this mail. The concurrent programming that does not bend the brain is not the true concurrent programming. > 1) [snip] > I'm suprised, however, by the following program. It creates threads B... > which immediately start to wait. I'd expect all threads will wait until the > cv is signalled, which won't happen, or until the program terminates. > Instead the waiting ends unexpectedly for every second thread. scenario: - CV::wait calls Mutex::exclusive_unlock - then exclusive_unlock unlocks m and yields to CV::wait, which suspends, _leaving m unlocked_ - next thread calls CV::wait -> calls excl. unlock -> 'return unless @locked' so the block passed to excl. unlock is never yielded... Thread is never suspended: wait ends immediately. Apperently CV::wait should use mutex.synchronize instead of exclusive unlock. This next scrap of code does not answer your CV question, but it works. require "semaphore" # see rt4997 Thread.abort_on_exception = true def test_cv(th_count) s = Semaphore.new th_count, 0 threads = [] th_count.times do |i| th = Thread.start do i_local = i puts "#{i_local} waiting" s.wait puts "#{i_local} waiting ends" end threads << th end puts "sleeping" sleep 2 puts "broadcast" th_count.times{ s.signal } puts "final sleep" sleep 2 puts "killing threads" threads.each { |th| th.exit } puts "threads killed", "" end 10.times do |th_count| test_cv(th_count+1) # skip the zero case end > 2) > Then my second problem. I think the current ConditionVariable is inadequate > for anything but the most basic needs. For example if one wants to use it to > create synchronization points there will be surprises. > > This example is borrowed from Dave&Andy's article about Mutual exclusions at > http://dev.rubycentral.com/articles/mutex.html. I modified it a bit to let > the "feature" show up. The trick is to get thread B's signal execute before > A will wait. For that I added couple lines of code to expose threads for > context switching, and run the test many times to be captured in the > deadlock eventually. [snip] Broken As Designed ;) The current CV implementation has an implicit precondition that wait is called before signal: there's no state kept about the number of signals received, any waiters are run and that's it. Your code obviously doesn't satisfy this precond (eventually). IOW: CV is the wrong primitive for what you want to achieve. > === > Lastly I have few ideas for the future versions. > > * I'd like to see a version of CV where signalling is queued > (optionally) so that cv.signal followed by later cv.wait > would not get lost. This solves the bug case 2 might be suffer. > * The first solution could be extended. The CV would count > signals, and CV#wait_for(n) is introduced. That would allow > one to write code like next example easily. > > A B C > cv.wait_for(2) | | > cv.signal | > | | > = | cv.signal > | | | In both cases a Semaphore would come in handy, though you'd have an upper limit to the number of signals kept. An unlimited sem is feasible anyway. > I'm not sure how CV#broadcast should be done. Maybe it should > release all waiters. > Second extension could be for CV#signal(n) to pass a integer with > approximately same semantics as n.times{cv.signal} (done atomicly). A sem.signal_all could do this. Regards, Michel