Problemställningar vid trådprogrammering
Så vad är problemet? Nu har vi tittat på vad en tråd
egentligen är och vi har gått igenom Javas tråd-API. Är
inte kursen slut nu? Nej, det är nu det börjar... De kommande
föreläsningarna handlar om beprövade lösningar på
vanliga problem som uppkommer vid trådprogrammering. Det här
är en ytterst kortfattad genomgång av vad vi vill uppnå
och vad som kan hindra oss att uppnå det.
Litteratur
Avsnitt 1.3 i Lea: Concurrent Programming in Java
Objekt och aktiviteter
Hittils (dvs i Javakursen) har ett program kunnat betraktas som en samling
objekt. Gärna grupperade på något finurligt sätt
och relaterande till varandra enligt någon klurig strategi. Detta
synsätt är förvisso sant även när det handlar
om flertrådade program, men det går då även att
se programmet som en samling av aktiviteter (till exempel metodanrop eller
trådar) vilka kan pågå parallellt och sträcka sig
över flera olika objekt. Dessa två synsätt leder till två
motsägelsefulla kriterier på ett bra objektorienterat program,
säkerhet och liveness (hur översätts det? livlighet? livfullhet?).
Säkerhet
Säkerhet innebär att det aldrig ska hända något
oönskat med något objekt. Det oönskade som kan hända
är att objektet hanmar i ett otillåtet tillstånd, till
exempel att dess fält har antagit värden som de aldrig får
ha. Detta händer vanligtvis genom att flera trådar försöker
använda fälten samtidigt (dvs kapplöpning).
För att definiera tillåtna tillstånd hos ett objekt
kan man ställa upp regler för hur fälten hos objektet (och
hos andra objekt som det är beroende av) ska förhålla sig
till varandra. Till exempel kan en regel för ett objekt som representerar
ett bankkonto vara att saldot aldrig får vara under noll. En regel
för ett objekt som representerar en vattentank kan vara att aktuellt
vatteninnehåll är mellan noll och tankens volym. En sådan
regel kallas invariant.
Detta finns matematiska metoder för att bevisa att ett system är
säkert. Detta faller dock definitivt utanför den här kursen.
En bok som på ett relativt lättfattligt sätt ger en introduktion
till sådant är Magge & Kramer: Concurrency: State Models
& Java Programs, Wiley 1999 (ISBN: 0-471-98710-7)
Lösningen på problemet att flera trådar samtidigt läser/skriver
till samma fält kunde vara att synkronisera alla metoder i alla objekt.
Den lösning förhindrar dock fullständigt det andra önskemålet
vi har på ett bra flertrådat program:
Liveness
Någonting kommer att hända (och gärna fort). Ett
minimikrav på liveness är att alla aktiviteter förr eller
senare fortsätter exekveringen. Exekveringen kan hejdas av väntan
på till exempel lås, händelser (wait()/notify()),
CPU-tid eller I/O. Detta kan leda till att en aktivitet helt stannar, till
exempel genom att två trådar båda väntar på
ett lås som den andra tråden äger (deadlock eller
passivt
dödläge) eller genom att en tråd aldrig får exekveringstid
i processorn (starvation eller svältning). En tråd
kan också bli kvar på en viss punkt i exekveringen om den fastnar
i en evig loop, till exempel testar ett villkor som aldrig blir sant eller
om och om igen anropar en metod som måste exekvera till slut men
bara kastar undantag istället. Detta kallas livelock eller
aktivt
dödläge.
Vi vill gärna utöka kravet på att något ska hända
till att det ska hända fort. När det handlar om trådprogrammering
gäller dock i än högre grad än annars att programmet
i första hand måste vara lättförståeligt och
säkert, därefter kommer prestanda. Prestanda för ett trådprogram
påverkas i hög grad av att det tar lång tid att utföra
operationer som: gå in i ett synchronized-block, anropa
wait()/notify()/notifyAll(),
skapa en ny tråd och byta exekverande tråd.
Ett viktigt måttet på prestanda är latency (response
time eller svarstid) vilket är den tid det tar innan användaren
märker att något börjar hända. Andra mått är
throughput,
hur snabbt något exekverar, och scalability, hur mycket latency
och throughput förbättras när ytterligare resurser (tex
fler CPU:er) tillförs.