#!/usr/bin/ruby -w # $Id: semaphore,v 1.3 2003/08/25 08:10:24 hip Exp $ 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 self 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(i) { |ii| snooze; client ii } 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