#!/usr/bin/ruby # The Santa Claus problem # wzieto w Allen B. Downey "The Little Book of Semaphores" # # Santa Claus spi w swojej pracowni na Polnocnym Biegunie i # moze zostac obudzony tylko jezeli: # (1) Wsystkie 9 reniferow wroca z wakacji # (2) Elfy maja problemy z produkowaniem zabawek # Zeby pozwolic Sant'ie sie wyspac elfy moga obudzic # go tylko jezeli trzy elfa maja problemy. Kiedy # Santa rozwiaze problemy tych 3 elfow i jezeli jacys # inny elfy maja problemy, to musza poczekac az tamci # 3 wroca. Co wiecej jezeli Santa budzac sie zobaczy # jednoczesnie 9 reniferow oraz 3 elfy, to zajmuje sie # reniferami, bo wazne jest dostarczyc prezenty na czas # (renifery maja zwyczaj wracac w ostatniej chwili). # Wracajace renifery czekaja az uzbiera sie ich 9 # i tylko pozniej zostaja umieszczone w sanie. Zakladamy # ze jest dowolna ilosc elfow ale tylko 9 reniferow # Semafor licznikowy zaimplementowany uzywajac wait i signal # zrodlo: http://www.imasy.or.jp/~fukumoto/ruby/semaphore.rb class CountingSemaphore def initialize(initvalue = 0) @counter = initvalue @waiting_list = [] end def wait Thread.critical = true if (@counter -= 1) < 0 @waiting_list.push(Thread.current) Thread.stop end self ensure Thread.critical = false end def signal Thread.critical = true begin if (@counter += 1) <= 0 t = @waiting_list.shift t.wakeup if t end rescue ThreadError retry end self ensure Thread.critical = false end alias down wait alias up signal alias P wait alias V signal def exclusive wait yield ensure signal end alias synchronize exclusive end Semaphore = CountingSemaphore elves = 0 # licznik elfow reindeer = 0 # licznik reniferow santa_sem = Semaphore.new # Santa czeka az 9 reniferow go zasygnalizuje # zmienne globalne musza sie zaczynac od $, inaczej nie sa widoczne w funkcjach # reszta zmiennych nie jest globalna, ale uzywa sie tylko w blokach (do..end, for..end i podobnych) $reindeer_sem = Semaphore.new # renifery czekaja az Santa kaze im isc do sani elf_tex = Semaphore.new(1) # elfy uzywaja tego semaforu zeby nie pozwolic wejsc wiecej niz 3 elfom $help_sem = Semaphore.new # elfy jak dostaja pomocy blokuja sie tutaj, Santa jak im pomoze puszcza ich require 'thread' mutex = Mutex.new # chroni dostep do elves i reindeer # renifer jedzie na wakacje # sleep pobiera czas w sekundach ale jako float # rand zwraca wartos 0..1, wiec wakacje trwaja od 0 do 2 sekund def reindeer_vacation sleep(2.0*rand) end # elf spia i nie potrzebuja pomocy def elf_sleep sleep(2.0*rand) end # Santa wstaje i przygotowuje sanie, puszczajac wszystkich reniferow # renifery jada i rozdaja prezenty (po tej funkcji zakladamy ze juz rozdali) def prepare_sleigh sleep(2.0*rand) for i in 1..9 $reindeer_sem.signal end end # renifery czekaja az Santa ich umiesci w sanie def get_hitched $reindeer_sem.wait end # Santa pomaga trzem elfom i puszcza ich def help_elves sleep(2.0*rand) $help_sem.signal $help_sem.signal $help_sem.signal end # Elfy czekaja az Santa im pomoze def get_help $help_sem.wait end # Watek Santy santa_thread = Thread.new do while true puts "Santa spi" santa_sem.wait puts "Ktos obudzil Sante" mutex.synchronize do if reindeer == 9 puts "Jest 9 reniferow Santa idzie ich umiescic w sanie" reindeer = 0 prepare_sleigh puts "Santa umiescil 9 reniferow w sanie" elsif elves == 3 puts "Santa idzie pomoc 3 elfom z zabawkami" help_elves puts "Santa skonczyl pomagac elfom z zabawkami" end end end end # Watki reniferow reindeer_threads = [] for i in 1..9 reindeer_threads << Thread.new(i) do |nr| while true puts "renifer #{nr} jedzie na wakacje" reindeer_vacation puts "renifer #{nr} przyjechal z wakacje i czeka az Santa go umiesci w sanie" mutex.synchronize do reindeer += 1 if reindeer == 9 santa_sem.signal end end get_hitched end end end # Watki elfow nelves = 3 elves_threads = [] for i in 1..nelves elves_threads << Thread.new(i) do |nr| while true puts "elf #{nr} chce pomocy z zabawkami" elf_tex.wait mutex.synchronize do elves += 1 if elves == 3 puts "jest trzech elfow budzimy Sante" santa_sem.signal else elf_tex.signal end end get_help mutex.synchronize do elves -= 1 if elves == 0 elf_tex.signal end end elf_sleep end end end # W Ruby jesli glowny watek nie ma nic do roboty to nie czeka na inne # watki i umiera, wiec musimy czekac az sie skonczy watek Santy # (a nie skonczy sie nigdy), wiec program tez sie nigdy nie skonczy # chyba ze wcisniemy Ctrl-C santa_thread.join