|
|||
Forrige < |
Innhold ^
|
Neste >
|
require 'net/http' pages = %w( www.rubycentral.com www.awl.com www.pragmaticprogrammer.com ) threads = [] for page in pages threads << Thread.new(page) { |myPage| h = Net::HTTP.new(myPage, 80) puts "Fetching: #{myPage}" resp, data = h.get('/', nil ) puts "Got #{myPage}: #{resp.message}" } end threads.each { |aThread| aThread.join } |
|
Fetching: www.rubycentral.com Fetching: www.awl.com Fetching: www.pragmaticprogrammer.com Got www.rubycentral.com: OK Got www.pragmaticprogrammer.com: OK Got www.awl.com: OK |
Thread.new
-kallet.
Det blir gitt en blokk som inneholder koden som skal kjøres i den nye tråden. I vår tilfelle bruker
blokken net/http
-biblioteket for å få tak i topp-siden fra hver av våre nominerte
sider. Vår sporing viser klar at disse hentingene går parallellt.
Når vi lager tråden sender vi den ønskede HTML-siden inn som en parameter.
Denne parameteren blir sendt inn til blokken som myPage
. Hvorfor gjør
vi det, i stedet for å bruke verdien til variabelen page
i
blokken?
En tråd deler alle globale, instans- og lokale variabler som eksisterer
på det tidspunktet tråden startes. Som alle med en lillebror kan fortelle deg,
så er deling ikke alltid en god ting. I dette tilfellet ville alle disse tre
trådene dele variabelen page
. Den første tråden starter, og
page
blir satt til http://www.rubycentral.com. I mellomtiden
så går løkken som starter trådene. Andre gangen blir page
satt til
http://www.awl.com. Hvis den første tråden ikke har avsluttet med å bruke
page
-variabelen så vil den plutselig starte å bruke sin nye verdi.
Slike feil er vanskelige å spore opp.
Imidlertid så er lokale variabler som blir laget innen en tråds blokk virkelige
lokale for den tråden---hver tråd vil ha sin egen kopi av disse variablene.
I vårt tilfelle vil variabelen myPage
bli satt på tidspunktet
trådenen blir startet, og hver tråd vil ha sin egen kopi av side-adressen.
join
på hver av de trådene vi laget?
Når et Ruby-program terminerer blir alle løpende tråder avbrutt, uavhengig av
deres tilstander. Imidlertid kan du vente på at en spesiell tråd skal avsluttes ved å kalle
trådens
Thread#join
-metode.
Den kallende tråden vil blokkere til den angitte tråd er ferdig. Ved å kalle
join
på hver av forespørselstrådene kan du forsikre at alle tre forespørslene har
avsluttet før du terminerer hovedprogrammet.
I tillegg til join
, er det noen få andre hendige rutiner som blir benyttet til
å manipulere tråder. Først av alt, den nåværende tråden er alltid tilgjengelig ved å bruke
Thread.current
.
Du kan få en liste over alle Thread
-objekter som er kjørbare eller
stopppet. For å bestemme status på en enkelt tråd can du bruke
Thread#status
og
Thread#alive?
.
Du kan også justere prioriteten til en tråd ved å bruke
Thread#priority=
.
Høyprioritetstråder vil bli kjørt foran lavprioritetstråder. Vi vil snakke mer om trådfordeling
og stopping og starting av tråder om kort tid.
Thread
inneholder en spesiell fasilitet
som tillater tråd-lokale variabler å bli laget og aksessert ved navn. Du behandler ganske enkelt
tråd-objektet som om det var en Hash
, som skriver til elementene ved å bruke
[]=
og leser dem tilbake ved å bruke []
. I dette eksemplet registrerer
hver tråd den nåværende verdien til variabelen count
i en tråd-lokal variabel med
nøkkelen mycount
. (Det er et spesielt forhold (race condition) i denne koden, men vi har ikke snakket
om synkronisering ennå, så vi ignorerer det foreløpig):
count = 0 arr = [] 10.times do |i| arr[i] = Thread.new { sleep(rand(0)/10.0) Thread.current["mycount"] = count count += 1 } end arr.each {|t| t.join; print t["mycount"], ", " } puts "count = #{count}" |
8, 0, 3, 7, 2, 1, 6, 5, 4, 9, count = 10 |
count
som ble fanget opp av hver enkelt. For å gjøre det mer interessant
har vi satt hver tråd til å vente en tilfeldig tid før verdien registreres.
abort_on_exception
-flagget som er dokumentert på sidene 384 og 387.
Hvis abort_on_exception
er false
, som er standard,
vil et uhåndtert unntak bare drepe den nåværende tråden---alle de andre vil forsette å
kjøre. I det følgende eksempelet går tråd nummer 3 i lufta og feiler i å produsere noe
output. Imidlertid kan du fortsatt se sporet fra de andre trådene.
threads = [] 6.times { |i| threads << Thread.new(i) { raise "Boom!" if i == 3 puts i } } threads.each {|t| t.join } |
01 2 45 prog.rb:4: Boom! (RuntimeError) from prog.rb:8:in `join' from prog.rb:8 from prog.rb:8:in `each' from prog.rb:8 |
abort_on_exception
til true
og et uhåndtert unntak
vil drepe alle tråder som kjører. Med en gang tråd 3 dør, vil det ikke bli produsert noe
output.
Thread.abort_on_exception = true threads = [] 6.times { |i| threads << Thread.new(i) { raise "Boom!" if i == 3 puts i } } threads.each {|t| t.join } |
01 2 prog.rb:5: Boom! (RuntimeError) from prog.rb:7:in `initialize' from prog.rb:7:in `new' from prog.rb:7 from prog.rb:3:in `times' from prog.rb:3 |
Thread
tilbyr en rekke metoder for å kontrollere tråd-kjøreren.
Ved å kalle
Thread.stop
stoppes den tråden som nå kjører, mens
Thread#run
setter opp for at en spesiell tråd skal bli kjørt.
Thread.pass
pauser den nåværenede tråden slik at andre tråder kan få kjøre.
Thread#join
og
Thread#value
suspenderer den kallende tråden inntil den angitte tråden avsluttes.
Vi kan demonstrere disse egenskapene i det følgende, meningsløse programmet.
t = Thread.new { sleep .1; Thread.pass; Thread.stop; }
|
||
t.status
|
» |
"sleep"
|
t.run
|
||
t.status
|
» |
"run"
|
t.run
|
||
t.status
|
» |
false
|
true
(ved å bruke
Thread.critical=
-metoden)
vil tråd-kjøreren ikke sette igang noen eksisterende tråd for kjøring. Imidlertid vil ikke
dette hindre nye tråder i fra å bli laget og kjørt. Visse tråd-operasjoner (som å stoppe
eller drepe en tråd, få den nåværende tråden til å sove, eller å fremme et unntak) kan få en tråd
til å bli satt opp for kjøring selv når man er i en kritisk seksjon.
Det er mulig å bruke
Thread.critical=
direkte, men det er ikke særlig praktisk. Heldigvis har Ruby flere alternativer. To av de beste alternativene er klassen Mutex
og klassen ConditionVariable
, som er
tilgjengelig i thread
-biblioteksmodulen. Se dokumentasjonen som begynner på side 457.
Mutex
er en klasse som implementerer en enkel semafor-lås for
å gi ekslusiv tilgang til en delt ressurs. Det betyr at kun en tråd kan holde låsen
på et gitt tidspunkt. Andre tråder må velge mellom å vente i kø på at låsen skal bli tilgjengelig,
eller å få en umiddelbar feil som indikerer at låsen ikke er tilgjengelig.
En mutex blir ofte brukt når oppdatering på delte data trenger å være atomisk.
La oss si at vi trenger å oppdatere to variabler som del av en transaksjon. Vi kan simulere dette
i et trivielt program ved å inkrementere noen tellere. Oppdateringene er forventet å
være atomiske---resten av verden skal aldri se tellerne med forskjellige verdier. Uten noen
type mutex-kontroll vill ikke dette virke.
count1 = count2 = 0
|
||
difference = 0
|
||
counter = Thread.new do
|
||
loop do
|
||
count1 += 1
|
||
count2 += 1
|
||
end
|
||
end
|
||
spy = Thread.new do
|
||
loop do
|
||
difference += (count1 - count2).abs
|
||
end
|
||
end
|
||
sleep 1
|
||
Thread.critical = 1
|
||
count1
|
» |
189263
|
count2
|
» |
189263
|
difference
|
» |
90500
|
count1
og count2
var inkonsistente.
Heldigvis kan vi fikse dette ved å bruke en mutex.
require 'thread' mutex = Mutex.new count1 = count2 = 0 difference = 0 counter = Thread.new do loop do mutex.synchronize do count1 += 1 count2 += 1 end end end spy = Thread.new do loop do mutex.synchronize do difference += (count1 - count2).abs end end end |
sleep 1
|
||
mutex.lock
|
||
count1
|
» |
17075
|
count2
|
» |
17075
|
difference
|
» |
0
|
require 'thread' mutex = Mutex.new cv = ConditionVariable.new a = Thread.new { mutex.synchronize { puts "A: I have critical section, but will wait for cv" cv.wait(mutex) puts "A: I have critical section again! I rule!" } } puts "(Later, back at the ranch...)" b = Thread.new { mutex.synchronize { puts "B: Now I am critical, but am done with cv" cv.signal puts "B: I am still critical, finishing up" } } a.join b.join |
A: I have critical section, but will wait for cv(Later, back at the ranch...) B: Now I am critical, but am done with cv B: I am still critical, finishing up A: I have critical section again! I rule! |
monitor.rb
og sync.rb
i lib
-underkatalogen til distribusjonen.
system
og `` (backquote).
system("tar xzf test.tgz")
|
» |
true
|
result = `date`
|
||
result
|
» |
"Sun Nov 25 23:43:35 CST 2001\n"
|
Kernel::system
kjører den gitte kommandoen i en subprosess; den returnerer true
hvis kommandoen
ble funnet og kjørt skikkelig, ellers false
. Ved feil vil du finne
subprosessens utgangskode i den globale variabelen $?
.
Et problem med system
er at kommandoens output vil ganske enkelt gå til samme destinasjon
som ditt programs output, hvilket ikke nødvendigvis er ønskelig.
Får å fange standard-outputen til en
sub-prosess kan du bruke backquotes som med `date`
i det foregående eksempelet.
Husk at du kan trenge å fjerne linjeskift-tegnene fra resultatet ved å bruke
String#chomp
metoden.
Ok, dette er greitt nok for enkle tilfeller---vi kan kjøre en annen prosess og få retur-statusen.
Men ofte behøver vi litt mer kontroll enn som så. Vi ønsker å kunne ha en samtale med
subprosessen, og muligens både sende data til den og motta data tilbake.
Metoden
IO.popen
gjør akkurat
dette. popen
-metoden kjører en kommando som en sub-prosess og kobler seg til den subprosessens
standard input og standard output til et Ruby IO
-objekt.
Skriv til IO
-objektet og subprosessen kan lese det på standard input. Hva enn
sub-prosessen skriver er tilgjengelig i Ruby-programmet ved å lese det fra IO
-objektet.
For eksempel er et av de mer nyttige verktøyene på systemene våre pig
, et program
som leser ord fra standard input og skriver dem i "pig Latin" (en engelsk variant av "røverspråk"). Vi kan bruke dette når våre
Ruby-programmer trenger å sende oss output som vår femåring ikke skal forstå.
pig = IO.popen("pig", "w+") pig.puts "ice cream after they go to bed" pig.close_write puts pig.gets |
iceway eamcray afterway eythay ogay otay edbay |
pig
-programmet ikke skyller ut outputen
det skriver. Vårt første forsøk på dette eksempelet, som hadde et
pig.puts
fulgt av et pig.gets
, hang for alltid.
pig
-programmet prosesserte vår input, men svaret ble aldri skrevet til røret.
Vi måtte legge inn pig.close_write
-linjen. Dette sender en slutt-på-fil beskjed
til pig
sin standard input, og når
pig
terminerer skylles outputen ut til vårt kallende program.
Det er en ting til med popen
. Hvis kommandoen du sender den
er et enkelt minus-tegn (``--''), vil popen
sette igang en ny
Ruby-interpreter. Både denne og den opprinnelige interpreteren vil fortsette
å kjøre ved å returnere fra popen
. Den orgininale prosessen
vil motta et IO
-objekt tilbake, mens barnet vil
motta nil
.
pipe = IO.popen("-","w+") if pipe pipe.puts "Get a job!" $stderr.puts "Child says '#{pipe.gets.chomp}'" else $stderr.puts "Dad says '#{gets.chomp}'" puts "OK" end |
Dad says 'Get a job!' Child says 'OK' |
popen
, er de tradisjonelle Unix-kallene,
Kernel::fork
,
Kernel::exec
, and
IO.pipe
tilgjengelige på plattformer som støtter dem. Konvensjonene til filnavn vil hos mange
IO
-metoder og
Kernel::open
også føre
til subprosesser hvis du putter en ``|
'' som det første tegnet i filnavnet
(se introduksjonen til klassen IO
på side 325 for detaljer).
Merk deg at du kan ikke lage rør ved å bruke
File.new
;
den er kun ment for filer.
exec("sort testfile > output.txt") if fork == nil # Sorteringen skjer nå i en barneprosess # Fortsett med prosessering i hovedprogrammet # Vent deretter på at sorteringen skal bli ferdig Process.wait |
Kernel::fork
returnerer en prosess-id i foreldren, og nil
i barnet, så barneprosessen vil
utøve
Kernel::exec
-kallet
og kjøre sortering. Noe senere kjører vi et
Process::wait
-kall,
som venter på at sorteringen skal bli ferdig (og returnerer dens prosess-id).
Hvis du heller vil få beskjed når et barn lukkes (istedenfor å bare vente på
det), kan du sette opp en signalhåndterer som bruker
Kernel::trap
(beskrevet på side 427). Her setter vi opp en felle på SIGCLD
, som er
signalet sendes ved "død-barneprosess".
trap("CLD") { pid = Process.wait puts "Child pid #{pid}: terminated" exit } exec("sort testfile > output.txt") if fork == nil # gjør noe annet... |
Child pid 19326: terminated |
IO.popen
arbeider med en blokk
på mye av samme måten som
File.open
gjør. Send en kommando til popen
, slik som date
, og blokken vil
bli sendt et IO
-objekt som parameter.
IO.popen ("date") { |f| puts "Date is #{f.gets}" } |
Date is Sun Nov 25 23:43:36 CST 2001 |
IO
-objektet vil bli lukket automatisk når kodeblokken lukkes,
akkurat som det er med
File.open
.
Hvis du assosierer en blokk med
Kernel::fork
vil koden i blokken bli kjørt i en Ruby subprosess, og foreldren vil fortsette etter blokken.
fork do puts "In child, pid = #$$" exit 99 end pid = Process.wait puts "Child terminated, pid = #{pid}, exit code = #{$? >> 8}" |
In child, pid = 19333 Child terminated, pid = 19333, exit code = 99 |
$?
åtte bits til høyre
før den blir vist? Dette er en "egenskap" i Posix-systemer: de nederste 8 bits
til en exit-kode inneholder grunnen til at programmet er terminert, mens de høyere
8 bitsene inneholder den egentlige utgangskoden.
( In progress translation to Norwegian by NorwayRUG. $Revision: 1.10 $ )
$Log: tut_threads.xml,v $ Revision 1.10 2003/08/31 11:58:58 kent Fjerner masse KapitaliseringsMani i overskrifter. Revision 1.9 2002/07/27 11:58:05 kent Småfiks. Revision 1.8 2002/07/27 11:54:18 kent Endel omskriving frem til "Blockker og Subprosesser". Revision 1.7 2002/07/26 17:06:12 kent Gjennomgang og småfiks frem til Mutex-klassen. Revision 1.6 2002/07/23 14:48:16 kent Kjapp overgang frem til abort_on_exception, fikser små skriveleif m.m.
Forrige < |
Innhold ^
|
Neste >
|