Processer och trådar: att jobba ensam kontra skuggklonstekniken

Kunskap som en inövad enminutspresentation på en jobbintervju

”Vad är skillnaden mellan en process och en tråd?”

När jag sökte mitt första jobb fanns den här frågan alltid med på listan ”topp 10 vanligaste intervjufrågorna”. Jag svarade som en maskin.

”En process är ett program som körs, och en tråd är en enhet av exekveringsflöde som körs inuti en process. Processer delar inte resurser, men det gör trådar.”

Intervjuaren nickade, och jag inbillade mig att jag hade förstått begreppet fullständigt. Men innan jag stötte på ”samtidighetsproblem” (Concurrency Issue) i jobbet hade jag ingen aning om hur skrämmande den meningen egentligen var.

”Varför ökar visningsräknaren bara med 98 när den borde öka med 100?” ”Varför fryser webbplatsen när någon startar en Excel-nedladdning?”

Min kod fungerade perfekt när jag körde den själv, men när flera användare kom in samtidigt blev allt ett enda kaos. Jag trodde att det räckte att lägga till fler ”arbetare”, men jag visste inte att kostnaden för att hantera dem (Context Switching) växer exponentiellt ju fler arbetarna blir.

När du jobbar ensam är det fridfullt, men när många jobbar tillsammans bryter kriget ut.

Fabriker och arbetare i det digitala logistikcentret

Låt oss återvända till vår värld, det ”digitala logistikcentret”. Att ett program körs i en dator är samma sak som att starta en ”verkstad” (Factory) inne i logistikcentret.

1. Process: en självständig verkstad

  • Definition: en ”självständig arbetsenhet” som har tilldelats resurser av operativsystemet (fabrikschefen).
  • Egenskaper: var och en har en egen byggnad. Även om fabriken bredvid rasar (ett fel) är min fabrik säker.
  • Kostnad: för att bygga en fabrik behöver du mark (minne) och måste registrera ett företag (PID). ”Dyrt och tungt.”
  • Delning: i princip kan du inte hämta saker från fabriken bredvid. (Då måste du använda en komplicerad kommunikation som kallas IPC.)

2. Tråd (Thread): arbetarna i verkstaden

  • Definition: den ”arbetare” som faktiskt utför jobbet inuti processen (verkstaden).
  • Egenskaper: en fabrik kan ha flera arbetare (multitrådning).
  • Kostnad: att anställa en ny arbetare är mycket ”billigare och lättare” än att bygga en ny fabrik.
  • Delning: det här är kärnan. Arbetarna delar på ”matsalen (Heap) och personalrummet (Data)”.
En tråd har samtidigt ett ”delat utrymme” (Heap) och ett ”eget utrymme” (Stack).

[Code Verification] Delandets tragedi (samtidighetsproblem)

”Trådar delar resurser.” På intervjun lät det som en fördel, men i verkliga projekt kan det bli fröet till en ”katastrof”.

Minns du heapen (Heap) som vi gick igenom förra gången? Trådarna delar på heapområdet. Det betyder att tråd B kan komma och skriva över data som tråd A håller på att arbeta med.

Det här kallas ett ”kapplöpningstillstånd” (Race Condition). Vi kollar det i kod.

public class RaceConditionTest {
    static int count = 0; // delad variabel som lagras i heapen (Heap)

    public static void main(String[] args) throws InterruptedException {
        // anställ två arbetare (trådar)
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 10000; i++) count++;
        });
        
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 10000; i++) count++;
        });

        t1.start(); // börja jobba!
        t2.start(); // börja jobba!
        
        t1.join(); // vänta tills den stämplar ut
        t2.join(); // vänta tills den stämplar ut
        
        System.out.println("Slutresultat: " + count);
    }
}

Förväntat resultat: två arbetare har lagt till 10 000 gånger var, så resultatet borde bli 20,000.

Faktiskt resultat: 15,482, 18,931… Värdet blir olika varje gång du kör programmet, och det blir inte 20 000.

Orsak:

  1. Tråd A läser count(100) från heapen och tar med det till stacken.
  2. Under tiden läser tråd B också count(100) från heapen.
  3. A ökar värdet till 101 och sparar det i heapen.
  4. B (som ju läste 100) ökar också värdet till 101 och sparar det i heapen.
  5. Det har lagts till två gånger, men resultatet blir som om det bara lagts till en gång.

Det här är vad som egentligen ligger bakom de buggar där ”data ibland försvinner” i verkliga system. Det är en tragedi som uppstår när arbetarna rör i samma huvudbok utan att prata med varandra.

Om man jämför situationen med en databas (DB) är det precis som ”flera frågor som skickas samtidigt utan transaktion (Transaction)”. Saldot på ett konto ändras samtidigt utan något skydd (Lock), och då inträffar den fruktansvärda olyckan att pengar bara försvinner. Precis som vi skyddar data i databasen med ROLLBACK eller COMMIT måste det också finnas en låsmekanism som Synchronized på kodnivå.

Trade-off i praktiken: flera processer kontra flera trådar

Så när ska du använda vad i praktiken?

1. Webbläsaren Chromes val: flera processer

I gamla Internet Explorer stängdes hela webbläsaren ner om en enda flik hängde sig (flertrådsmodellen). Chrome startar däremot varje flik som en ”separat process (fabrik)”.

  • Fördel: även om en flik slutar svara (fabriken sprängs) fungerar de andra flikarna (fabrikerna bredvid) som vanligt. (Stabilitet)
  • Nackdel: slukar enorma mängder minne. Det är därför Chrome har blivit en ”RAM-slukande flodhäst”. (Kostnad)

2. Webbservrarnas val (Spring, Node.js m.fl.): flera trådar

En server måste hantera förfrågningar från tusentals användare. Om du bygger en process (fabrik) för varje förfrågan kraschar servern. Därför har man massor av trådar (arbetare) i en enda process som tar hand om förfrågningarna.

  • Fördel: använder lite resurser och är snabbt. (Effektivitet)
  • Nackdel: om en enda tråd orsakar ett fel som dödar processen går hela servern ner. Dessutom måste utvecklaren själv förhindra samtidighetsproblemen vi såg nyss (Synchronized).
Ska du bygga en säker men dyr ”fästning” eller skicka upp snabba men riskabla ”drönare”?

Avslutning: med skuggklonstekniken följer ansvar

I dag har vi tittat på ”processer och trådar”, alltså hur arbetarna jobbar.

  • Process: en säker men dyr, självständig verkstad.
  • Tråd: en lätt och snabb medarbetare, men med risk för kollisioner.

Nu vet du att ”samtidighetsproblem” inte bara är ett ord för jobbintervjuer. Genom det delade lagret som kallas heapen (Heap) går oräkneliga trådarbetare ständigt in och ut. Att skapa ordning i det här kaoset är det som visar en backendutvecklares skicklighet.

Men vänta lite: när arbetarna blir fler, hur bestämmer man vems jobb som ska göras först? Hur hanterar fabrikschefen (OS) hundratals arbetare? Nästa gång ska jag prata om fabrikschefens mest besvärliga uppgift: ”schemaläggning och kontextbyte” (Context Switching).

Lämna en kommentar