1.4.1.17. fejezet, Párhuzamos programszálak
Szálat a Thread osztályból hozhatunk létre. Ha a new metódussal készítünk egy példányt, akkor egy blokkot hozzáfűzve elkészült az első szál, ami el is kezdte futását, méghozzá olyan gyorsan, hogy egy sleep metódust kellett utólag beilleszteni, hogy látszódjon az eredmény.
words = ["hello", "world", "goodbye", "mars" ] numbers = [1,2,3,4,5,6,7,8,9,10] startTime = Time.new puts( "Start: %10.9f" % startTime ) Thread.new{ words.each{ |word| puts( word ) } } Thread.new{ numbers.each{ |number| puts( number ) } } sleep(5) endTime = Time.new puts( "End: %10.9f" % endTime.to_f ) totalTime = endTime-startTime puts( "Total Time: %10.9f" % totalTime.to_f )
Eredmény:
Start: 1312620628.483902693 1 2 3 4 5 6 7 8 9 10 hello world goodbye mars End: 1312620633.484401941 Total Time: 5.000499085
Habár a Ruby szálkezelése nem natív, nem pre-emptive multitask-ingra épül, mint pl. a PHP5-ben, független az operációs rendszertől. Egy time-slicing metódussal osztja fel a szálak között a végrehajtáshoz szükséges időt. Ez várhatóan az 1.9-es változattal kezdődően megváltozik, de a 2.0-ás változatra biztos más alapokra fog épülni. Addig is nézzük, hogyan működik a jelenlegi megoldás, hátha nem lesz a változtatás olyan nagy mértékű, hogy teljesen más rendszer szerint kelljen programozni.
A fő szál
Habár még nem készítettünk egy szálat sem, a main szál a Ruby indításakor automatikusan keletkezik.
p( Thread.main )
Eredménye:
#<Thread:0x28955c8 run>
A szál azonosítóját láthatjuk, és az állapotát (run).
Szál státusz
run | a szál fut |
sleep | a szál alszik vagy I/O műveletre vár |
aborting | a szálat megszakították |
false | a szál normál kilépéskori státusza |
nil | kivétel keletkezett, a szál kilépett |
dead | kill metódussal leállítottuk a szál futását |
Szál státuszát a status metódussal kérdezhetjük le. Szálat megszakítani pedig a kill metódussal lehet. A szál futásáról a alive? metódussal győződhetünk meg. Szál feldolgozási idejéről lemondhatunk a pass metódussal, ezzel a következő szálnak adjuk tovább a vezérlést.
Szál futásának ellenőrzése
Futnak a szálak, de nem a leghelyesebb megoldás egy időzítésre alapozni, hogy a szál futását lássuk, és a futási időket kiszámítsuk. Szerencsére van egy civilizáltabb módja, hogy ellenőrzésünkbe tartsuk a szálak futását. A join metódus megköveteli a hívó száltól (például a fő száltól), hogy altassa saját magát, amíg a hívó szál futása befejeződik. Például:
words = ["hello", "world", "goodbye", "mars" ] numbers = [1,2,3,4,5,6,7,8,9,10] startTime = Time.new puts( "Start: %10.9f" % startTime ) wordsThread = Thread.new{ words.each{ |word| puts( word ) } } numbersThread = Thread.new{ numbers.each{ |number| puts( number ) } } [wordsThread, numbersThread].each{ |t| t.join } endTime = Time.new puts( "End: %10.9f" % endTime.to_f ) totalTime = endTime-startTime puts( "Total Time: %10.9f" % totalTime.to_f )
Az eredmény u.a. mint előbb.
Start: 1312622049.508980989 1 2 3 4 5 6 7 8 9 10 hello world goodbye mars End: 1312622049.509905338 Total Time: 0.000924278
Szál prioritása
A priority tulajdonsággal állítható a szál prioritása. Számoljuk ki az !50 értékét.
t3 = Thread.new{ 0.upto(50) {fac(50); print( "t3\n" )} } t3.priority = 10
Minél nagyobb az érték, annál több időt tölt az értelmező a megnövelt prioritású szálban. A main szál prioritása is állítható:
Thread.main.priority = 200
Tegyük fel, hogy készítünk három szálat (t1,t2,t3), és a prioritást állítjuk azért, hogy az első kettő előbb legyen kész, mint a harmadik. Ha a prioritás növelésével próbálkoznánk a t1 és t2 szálaknál, csalódottan észrevételeznénk, hogy a szálak szinte sorban hajtódnak végre (t3,t2,t1 sorrendben). Ha negatív számokkal próbálkozunk, 1.8-as Ruby-nál már meg is találtuk a megoldást. Legyen a prioritás t1 és t2-nél -1, t3-nál -2, és az eredmény, hogy a t1 és t2 párhuzamosan fut le, majd következik a t3. 1.9-es Ruby-ban viszont a t3 nagyobb prioritást kap, és végül a t1 és t2 versenyez a CPU-ért. Mi lehet az elsőnél a megoldás. Miért nem működik pozitív számokkal a prioritás. Egy magyarázata lehet ennek. Minden program minimum egy szálat indít, a main-t, aminek a kezdeti prioritása 0. Legyen mondjuk -1.
Thread.main.priority = -1
Mindegyik szál, amit eddig létrehoztunk, példányosítás után rögtön elkezdett futni. Most állítsuk le, és indítsuk újra.
t1 = Thread.new{ Thread.stop 0.upto(50){print( "t1\n" )} } t1.run
Mutextek
Előfordul, hogy egy szál szeretne hozzáférni egy globális változóhoz, vagy egy másik szállal közösen használt objektumhoz. A szálak összekeveredésének megelőzésére vezették be a mutexet, vagyis jelzőlámpát. Ha szabad jelzést mutat, birtokába veheti a közös erőforrást a szál. Tilos jelzésnél meg kell várnia, amíg az éppen lefoglaló szál elereszti a jelzőlámpát, és szabaddá válik.
require 'thread' $i = 0 semaphore = Mutex.new a = Thread.new { semaphore.synchronize{ 1000000.times{ $i += 1 } } } b = Thread.new { semaphore.synchronize{ 1000000.times{ $i += 1 } } } a.join b.join puts( $i )
Hasonlítsad össze a 13. fejezetben leírt Könyvtár tudakozó programot az alábbi könyvtár tudakozó programmal.
require 'find' $totalsize = 0 $dirsize = 0 def processFiles( baseDir ) Find.find( baseDir ) { |path| #Thread.pass # Thread.pass $dirsize += $dirsize # if a directory if (FileTest.directory?(path)) && (path != baseDir ) then print( "\n#{path} [#{$dirsize / 1024}K]" ) $dirsize = 0 else # if a file $filesize = File.size(path) print( "\n#{path} [#{$filesize} bytes]" ) $dirsize += $filesize $totalsize += $filesize end } end t1 = Thread.new{ Thread.stop processFiles( "/home/pzoli/" ) # <======= edit this directory name } t2 = Thread.new{ Thread.stop while t1.alive? do print( "\n\t\tProcessing..." ) Thread.pass end } t1.run t1.join t2.run printf( "\nTotal: #{$totalsize} bytes, #{$totalsize/1024}K, %0.02fMB\n\n", "#{$totalsize/1048576.0}" ) puts( "Total file size: #{$filesize}, Total directory size: #{$dirsize}" )
- A hozzászóláshoz be kell jelentkezni