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.