This might be a nice addition to the Ruby synchronisation primitives.
I welcome comments and improvements.
(shamelessly glarked and adapted from thread.rb)
---8<---
#!/usr/bin/ruby -w
class Semaphore
def initialize max = 1, init = max
fail ArgumentError, "maximum value must be > 0" unless max > 0
fail ArgumentError, "initial value must be >= 0" unless init >= 0
fail ArgumentError, "initial value must be <= maximum" unless init <= max
@waiting = []
@maximum = max
@value = init
end
def signal
return if @value == @maximum
Thread.critical = true
@value += 1
begin
t = @waiting.shift
t.run if t
rescue ThreadError
# nothing: tried to run a dead thread
end
Thread.critical = false
self
end
def wait
while(Thread.critical = true; @value == 0)
@waiting.push Thread.current
Thread.stop # implies Thread.critical = false
end
@value -= 1
Thread.critical = false
self
end
def synchronize
wait
begin
yield
ensure
signal
end
end
end
##########################################################################
if __FILE__ == $0
srand
def snooze
sleep rand 0
end
class Test1
def initialize
puts "* N processes contending for M resources, where N > M"
threads = []
@sem = Semaphore.new 3 # 3 resources available
for i in 0..9 do
threads.push Thread.start{ client i }
end
threads.each{ |t| t.join }
end
def client i
puts "client #{i}: wait"
@sem.wait
puts "client #{i}: running"
snooze
puts "client #{i}: signal"
@sem.signal
end
end
class Test2
def initialize
puts "* synchronous producer/consumer"
threads = []
@sin = Semaphore.new 1, 0
@sout = Semaphore.new 1, 0
@shared = 0
@N = 10
threads.push Thread.start{ producer }
threads.push Thread.start{ consumer }
threads.each{ |t| t.join }
end
def producer
for i in 1..@N do
snooze
@shared = i
puts "produced #{i}"
@sin.signal
@sout.wait
end
end
def consumer
for i in 1..@N do
snooze
@sin.wait
puts "consumed #{@shared}"
@sout.signal
end
end
end
class Test3
def initialize
puts "* asynchronous producer/consumer with finite (circular) buffer"
threads = []
@MAX = 3
@buffer = Array.new @MAX, 0
@input = 0
@output = 0
@elements = Semaphore.new @MAX, 0
@spaces = Semaphore.new @MAX
@N = 10
threads.push Thread.start{ producer }
threads.push Thread.start{ consumer }
threads.each{ |t| t.join }
end
def producer
for i in 1..@N do
snooze
@spaces.wait
@buffer[@input] = i
@input = (@input + 1) % @MAX
puts "produced #{i}"
@elements.signal
end
end
def consumer
for i in 1..@N do
snooze
@elements.wait
n = @buffer[@output]
@output = (@output + 1) % @MAX
puts "consumed #{n}"
@spaces.signal
end
end
end
class Test4
def initialize
puts "* transfer of control (coroutines)"
threads = []
@a = Semaphore.new 1, 0
@b = Semaphore.new 1, 0
@c = Semaphore.new 1, 0
threads.push Thread.new{ parent }
threads.push Thread.new{ process_a }
threads.push Thread.new{ process_b }
threads.each{ |t| t.join }
end
def parent
puts "P: 1"
@a.signal
@c.wait
puts "P: 6"
end
def process_a
@a.wait
puts "A: 2"
@b.signal
@a.wait
puts "A: 4"
@b.signal
end
def process_b
@b.wait
puts "B: 3"
@a.signal
@b.wait
puts "B: 5"
@c.signal
end
end
Test1.new
Test2.new
Test3.new
Test4.new
end
---8<---
regards,
Michel