On Wed, 04 Oct 2000  07:05:22 +0900, Aleksi Niemel? wrote:
> 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