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}" )