Processer och trådar

En introduktion till trådprogrammering. Diverse olika definitioner och begrepp som kommer att behandlas utförligt under kommande föreläsningar.

Litteratur

Detta avsnitt behandlas inte i kurslitteraturen.

Den som någon gång i livet vill dyka djupt in i operativsystemsteorier som till exempel processhantering gör klokt i att köpa nedanstående bok:
Silberschatz, Galvin, Gagne: Applied Operating System Concepts, Wiley 2000 (ISBN: 0-471-36508-4)

En bok om trådar i Java som tar upp väsentligt mer operativsystems- och hårdvaruaspekter än våran kursbok är:
Lewis, Berg: Multithreaded Programming with Java, Prentice Hall 2000 (ISBN: 0-13-017007-0)

Process

Vad är en process?

Långt tillbaks i tiden kunde ett operativsystem bara exekvera ett program åt gången (till exempel DOS, Windows 3.X). När man skrev ut något var hela datorn låst tills utskriften var klar. Den tiden är lyckligtvis förbi sedan länge, nuförtiden kan operativsystem exekvera flera program samtidigt. Till exempel lyssnar jag just nu på en skiva samtidigt som jag skriver denna text. Hur går det till? Min dator har bara en CPU men ändå tycks den exekvera ett musikprogram och ett ordbehandlingsprogram samtidigt.

Innan svaret på den frågan reflekterar vi över vad som egentligen utgör ett exekverande program. Tänk till exempel på programmen du körde på exkit i kursen Digital- och datorteknik förra året. Det behövs naturligtvis en programkod, maskininstruktioner som processorn läser in och exekverar. Dessutom behövs det lite minnesutrymme för till exempel variabler och stack. Programmet är naturligtvis även i högsta grad beroende av processorns register (programräknare, stackpekare osv). Ponera nu att operativsystemet (nu har vi lämnat exkit) reserverar ett utrymme i minnet för programkoden, dataarean och stacken samt dessutom en plats där innehållet i alla processorns register sparas. Innehållet i denna minnesarea beskriver till fullo programmet och den punkt i exekveringen det befinner sig på (det senare kallas programmets tillstånd). Så länge denna minnesarea är intakt spelar det ingen roll vad som händer med datorn eller vilken kod processorn exekverar. Det är bara att lägga tillbaks det registerinnehåll vi sparade så kommer processorn att fortsätta exekvera programmet exakt där den var när registren sparades.

Nu är det dags att besvara frågan hur flera program kan exekveras samtidigt av en enda processor. Svaret är att operativsystemet lägger upp en sådan minnesarea som vi nyss pratade om för varje program som körs, sedan växlar det mellan de olika programmen så snabbt att det upplevs som att alla program exekveras samtidigt. En sådan programexekvering kallas en process och minnesarean som beskriver den kallas bland annat process control block (PCB) eller process structure. Observera att varken maskinkoden, dataarean eller stacken behöver flyttas när en annan process ska börja exekvera. Det räcker att läsa in processorns register. Programräknaren kommer då att peka på kod i den nya processen och stackpekaren kommer att peka på den nya processens stack.

En process behöver alltså inte ta någon som helst hänsyn till andra processer. Varje process/program kommer att uppleva det som att den har processorn helt för sig själv när den exekverar.

Operativsystemet exekverar många fler processer än vad användarna har startat program, till exempel finns det normalt processer som hanterar nätverket och en process som exekverar när processorn inte har något annat att göra. Om du har Windows kan du starta aktivitetshanteraren så får du en lista över alla processer på datorn (På Windows NT måste du klicka på fliken processer). Har du Linux eller någon UNIX kan du skriva ps -ax för att få en sådan lista.

Processhantering

Ovanstående är en helt korrekt men kraftigt förenklad bild av processhanteringen i ett operativsystem, här kommer några exempel på den bistra (men intressanta) verkligheten.

Till exempel är det mycket mer än vad som ovan nämnts som måste sparas i en PCB. Bland annat måste den innehålla en lista över öppna filer, någon form av processnamn (för att den ska kunna anropas) och användarens identitet (för att avgöra vilka rättigheter den har).

Alla dessa PCB:er kommer att ligga i en massa köer och listor vilka ska struktureras och behandlas på något sätt. En stor fråga är i vilken ordning väntande processer ska få exekvera. Hur kan vi till exempel vara säkra på att viktiga processer får exekvera först? Att det inte finns processer som aldrig får exekvera? Att inte vissa användare upplever datorn som onödigt slö därför att deras processer får lite exekveringstid? När och hur länge processerna får exekvera kallas skedulering (scheduling).

Om exekverande process byts endast vid bestämda punkter i programmet (till exempel när den själv begär det eller när den utför någon form av I/O) kallas det frivilligt byte eller non-preemptive sheduling. Om processerna kan bytas när som helst (vanligtvis genom att någon tidskrets generar ett avbrott med jämna mellanrum och avbrottsrutinen byter process) kallas det ofrivilligt byte eller preemptive scheduling.

Nu förtiden är det inte ovanligt att en dator har flera processorer. Allt vad som här nämnts gäller även då men det blir ytterliggare mer komplicerat. Till exempel måste skeduleringen ske så att det inte står processer i kö till en CPU medans andra CPU:er är sysslolösa. Alla CPU:er måste jobba ungefär lika mycket (load balancing). Om alla CPU:er har en egen cache (vilket de har) är det väldigt klurigt att utarbeta rutiner för att försäkra sig om att de har samma uppfattning om hur minnet ser ut, det ska inte finnas olika värden på samma variabel i olika cacher. Dessa rutiner kallas minnesmodell. Om datorn har mer än en CPU kan flera processer verkligen exekveras i ett och samma ögonblick, detta kallas parallell exekvering. Om datorn endast har en CPU kan flera processer visserligen vara igång men i ett visst ögonblick kan endast en av dem exekveras. Det kallas concurrent (samtidig) exekvering.

Trådar

Vad är en tråd?

Om en process är allt som behövs för att exekvera ett program kan vi betrakta en tråd som allt som behövs för att beskriva programmets tillstånd och därmed självständigt exekvera en del av programmet. En process hade (bland annat) egen programkod, dataarea, stack och en kopia av CPU-registren. Varje process kan bestå av flera trådar som exekveraras samtidigt, varje tråd har sin egen minnesarea på samma sätt som en process. Trådens minnesarea innehåller dock varken programkod eller data. Alla trådar inom en process har tillgång till samma programkod och arbetar med samma (globala) variabler. Trådens minnesarea innehåller bara en stack (för återhoppsadresser vid metodanrop och för lokala variabler) samt utrymme för att spara processorns register. Åtminstone är det allt vi är intresserade av på det här stadiet, i verkligheten finns där även trådens namn och lite annat.

En process är som sagt ett eget program som exekverar sin egen kod. Alla trådar i processen exekverar däremot samma program (eftersom de inte har något eget minnesutrymme för programkod utan alla använder processens programkod. För övrigt gäller allt sagts om minneshantering, skedulering och köer även för trådar, lika väl som för processer.

Det finns många programbibliotek som tillhandahåller metoder för att skapa och hantera trådar. Med hjälp av dessa kan trådade program skrivas i de flesta språk. Java är däremor ett av de realtivt få språk där trådhanteringen är inbyggd i själva språket och därför alltid finns tillgänglig.

Ett första exempel

Nu är det dags att skriva ett litet flertrådat program. I Java hanteras en tråd av klassen java.lang.Thread. Den klassen innehåller en tom metod, public void run().

En tråd kan skapas på två sätt:

  1. Ärv av klassen Thread och omdefinera metoden run(). Den omdefinerade metoden run() kommer att exekveras i en egen tråd när vi anropar metoden start() i vår klass som ärvde av Thread.
  2. Precis som ovan men istället för att ärva av Thread och omdefinera run() skriver vi en klass som implementerar gränssnittet java.lang.Runnable (innehåller endast en metod, public void run()) och skickar med den till konstruktorn när vi skpar ett objekt av klassen Thread. När vi nu anropar start() kommer koden i vår run() att exekveras i en egen tråd.
Den senaste metoden är bäst. Dels är vi fria att ärva av andra klasser om vi vill (vilket inte går om vi redan har ärvt av klassen Thread), dels kan vi återanvända vår Runnable och skicka den till olika Thread-objekt om vi vill, dels kan vi i vissa fall välja att exekvera vår Runnable som en tråd genom att skicka den till en instans av klassen Thread medans vi i andra fall kan välja att inte köra den i en egen tråd utan anropa run() direkt.

Här kommer programmet, HelloWorldThread.java. Observera att metoden run() aldrig anropas. Den börjar automatiskt exekveras i sin egen tråd när vi anropar start(). Observera också att det bara finns en run(). Alla trådarna exekverar samma kod.

Varför använda trådar?

Anledningen till att dela upp ett program i flera trådar istället för att starta flera olika delprogram i olika processer är ett det går väldigt mycket fortare att skapa och byta trådar än att skapa och byta processer, bland annat på grund av att trådar inte har egna program- och dataareor i minnet. Men varför över huvud taget införa parallellitet i ett program? Här kommer några anledningar:
  1. Kortare svarstid. För till exempel en webserver är det viktigt att snabbt ge svar till klienterna. Om en ny klient skulle behöva vänta tills alla som anropat tidigare var helt klara skulle det kunna ta väldigt lång tid att få något som helst svar. Det skulle kanske verka som om servern inte var igång. Då är det bättre att skicka svar till flera klienter samtidigt (i olika trådar).
  2. Snabbare exekvering. Med flera trådar kan CPU:erna i en dator med mer än en CPU utnyttjas effektivare.
  3. Slippa vänta på I/O. Genom att placera I/O i en egen tråd kan processorn arbeta med något annat samtidigt utan att behöva vänta på att I/O-hanteringen avslutas.
  4. Simulering. Trådar kan användas för att simulera verkliga parallella förlopp.
Det var några exempel, fler finns på sid 20 i kursboken.