|
|||
| 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 >
|