Sedan några år tillbaka kan vi som utvecklare inte längre förlita oss på att processortillverkarna ska släppa processorer med snabbare och snabbare processorkärnor. Värmeutveckling och strömförbrukning är de tydigast bidragande orsakerna till detta. Istället är det nu antalet processorkärnor som tillverkarna tävlar med.
För att dra full nytta av dessa nya processorer måste vi programmerare anpassa oss och utforma programmen för de nya arkitekturerna. Programmering av system som exekverar kod parallellt brukar oftast uppfattas som svårt. Ett antal otrevliga fenomen som inte existerar i rent sekventiella världen program uppstår helt plötsligt. Dead-locks, race-conditions och livelocks är exempel på några sådana.
En stor anledning till att många tycker att denna typ av programmering är svår tror jag ligger i hur vi en gång lärt oss att programmera. Hur många programmeringsböcker eller kurser har du läst där parallell programmering avhandlas redan i första kapitlet eller under första kurstillfället? När vi sedan försöker applicera vårt sekventiella sätt att tänka på parallella problem i andra halvan av boken eller senare i kursen blir det genast svårt och krångligt.
Nedan tänkte jag kort presentera några olika tekniker för att dra nytta av de nya multi-core-processorerna.
Flera exekverande processer/trådar som kommunicerar via delat minne
Detta är den klassiska teknik som ofta dyker upp när parallell programmering diskuteras. För att undvika att flera processer skriver och läser till/från samma resurs samtidigt används t.ex. semaforer. Genom användande av dessa går det att se till så att bara en tråd eller process i taget har åtkomst till den skyddade resursen.
Semaforerna ställer dock till nya problem eftersom felaktig användning av dem kan leda till dead-locks och live-locks då flera processer får vänta i evighet på att en resurs ska bli ledig. Något som inte sällan resulterar i att hela systemet hänger sig.
Denna typ av problem är oftast väldigt svåra att återskapa på ett deterministiskt sätt och således oftast väldigt svåra att felsöka. Intel, Microsoft med flera marknadsför ett antal produkter som ska hjälpa till att undvika just denna typ av problem. Just detta är kanske ett sjukdomssymptom i sig, att vi anses behöva speciella verktyg för att överhuvudtaget kunna skriva korrekt kod.
Fler exekverande processer/trådar som kommunicerar via kopierat minne
Med denna teknik delas inga resurser mellan olika exekveringsinstanser. Istället kopieras variabler mellan de olika processerna när de behöver dela med sig av data. Data skickas ofta mellan processerna i så kallade signaler.
Denna typ av design gör att race conditions till stor undviks naturligt och att semaforer inte längre behövs. Detta minskar i sin tur risken för dead-locks och andra liknande fenomen. Även om dead-locks fortfarande kan uppstå är det oftast mycket lättare att förstå hur de uppstår, och undvika dem, eller rätta dem när de väl uppstår. Det finns flera programmeringsspråk som är konstruerade kring denna programmeringsparadigm, som den kanske kan kallas. Erlang är ett av de tydligaste exemplen på ett sådant språk.
Erlang använder sig av så kallade lättviktsprocesser som till skillnad från vanliga trådar och processer använder minimalt med resurser. Tack vare detta är det inget problem att skriva ett program som består av 10000-tals processer. Detta inbjuder till möjligheten att skriva program på helt nya sätt där processer skapas och dör kontinuerligt under programmets livslängd. Jag får återkomma till hur detta påverkar programstrukturen i ett senare inlägg.
Det finns två huvudsakliga problem med denna typ av program som jag ser det. Dels passar inte denna paradigm för alla typer av program och problem. GUI-programmering brukar ofta lyftas fram som ett sådant område. Dels är det svårt att anpassa ett redan befintligt system till denna paradigm. Systemen måste oftast designas enligt detta tankesätt från scratch.
Att kopiera minne istället för att dela det är dock ingen som kräver särskilda programmeringsspråk eller några speciella paradigm.
Virtualisering
Virtualisering kan ibland vara en enkel väg för att anpassa ett redan befintligt system till en multi-core-arkitektur. En s.k. hypervisor ser till att varje processorkärna upplevs som en egen enhet av applikationen. Flera instanser av applikationen kan då köras på samma processor utan att några särskilda anpassningar behöver göras av applikationen i sig.
Om systemet redan är distribuerad till sin natur kan detta vara ett mycket kostnadseffektivt sätt att anpassa det till multi-core. Det som tidigare var en nod i systemet som kördes på egen hårdvara körs nu tillsammans med andra noder på samma processor. Nackdelen med denna lösning är naturligtvis att den inte lämpar sig för alla typer av applikationer. Dessutom kan viss prestanda gå förlorad för varje enskild processorkärna i.o.m. det extra mjukvarulager som hypervisorn innebär.
Oavsett om du väljer någon av ovanstående filosofier eller någon helt annan så är det viktigt att börja fundera kring hur multi-core ska utnyttjas bäst för just ditt system. Multi-core är inte längre bara ett serverfenomen utan börjar nu att dyka upp i allt fler inbyggda system också. Snart har du det i din mikrovågsugn!
-- Tobias Gustafsson

