Exekveringstråd (tråd; från engelska thread -thread) - den minsta bearbetningsenheten, vars exekvering kan tilldelas av operativsystemets kärna . Implementeringen av trådar och processer skiljer sig åt mellan operativsystem, men i de flesta fall ligger exekveringstråden i en process. Flera exekveringstrådar kan finnas inom samma process och dela resurser som minne , medan processer inte delar dessa resurser. I synnerhet delar exekveringstrådar en process instruktionssekvens (dess kod) och dess kontext , värdena för variabler (processorregister och call stack ) som de har vid varje given tidpunkt.
Som en analogi kan trådarna i en process liknas vid att flera kockar arbetar tillsammans. De lagar alla samma rätt, läser samma kokbok med samma recept och följer dess instruktioner, och inte nödvändigtvis alla läser på samma sida.
På en enda processor sker multithreading vanligtvis genom tidsmultiplexering (som i fallet med multitasking ): processorn växlar mellan olika exekveringstrådar. Denna kontextväxling sker vanligtvis tillräckligt ofta för att användaren ska uppfatta utförandet av trådar eller uppgifter som samtidiga. I system med flera processorer och flera kärnor kan trådar eller uppgifter faktiskt köras samtidigt, där varje processor eller kärna bearbetar en separat tråd eller uppgift.
Många moderna operativsystem stöder både tidsdelning från processplaneraren och multiprocessortrådar för exekvering. Operativsystemets kärna tillåter programmerare att styra exekveringstrådarna genom ett systemanropsgränssnitt . Vissa kärnimplementationer hänvisar till det som en kärntråd , medan andra hänvisar till det som en lättviktsprocess ( LWP ), vilket är en speciell typ av kärntråd som delar samma tillstånd och data.
Program kan ha exekveringstrådar för användarutrymme när trådar skapas med hjälp av timers, signaler eller andra metoder för att avbryta exekveringen och skapa tidsdelning för en specifik situation ( Ad hoc ).
Exekveringstrådar skiljer sig från traditionella multitasking- operativsystemprocesser genom att:
System som Windows NT och OS/2 sägs ha "billiga" trådar och "dyra" processer. På andra operativsystem är skillnaden mellan exekveringstrådar och processer inte lika stor, förutom kostnaden för adressutrymmesbyte, som involverar användningen av en associativ översättningsbuffert .
Multithreading, som en utbredd programmerings- och kodexekveringsmodell, tillåter flera trådar att köras inom en enda process. Dessa exekveringstrådar delar på resurserna i en process, men kan också köras på egen hand. Den flertrådade programmeringsmodellen ger utvecklare en bekväm abstraktion av parallellt exekvering. Den kanske mest intressanta tillämpningen av tekniken är dock när den appliceras på en enda process, vilket tillåter dess parallellkörning på ett multiprocessorsystem .
Denna fördel med ett flertrådigt program gör att det kan köras snabbare på datorsystem som har flera processorer , en processor med flera kärnor eller på ett kluster av maskiner, eftersom programtrådar naturligtvis lämpar sig för verkligt parallell exekvering av processer. I det här fallet måste programmeraren vara mycket försiktig för att undvika tävlingsförhållanden och annat icke-intuitivt beteende. För att korrekt manipulera data måste exekveringstrådar ofta gå igenom en mötesprocedur för att behandla data i rätt ordning. Exekveringstrådar kan också behöva mutexes (som ofta implementeras med semaforer ) för att förhindra att delad data modifieras samtidigt eller läses under modifieringsprocessen. Slarvig användning av sådana primitiver kan leda till ett dödläge .
En annan användning av multithreading, även för enprocessorsystem, är möjligheten för en applikation att svara på input. I enkeltrådade program, om huvudtråden för exekvering blockeras av exekvering av en långvarig uppgift, kan hela programmet vara i ett fruset tillstånd. Genom att flytta sådana långvariga uppgifter till en arbetartråd som löper parallellt med huvudtråden, blir det möjligt för applikationer att fortsätta svara på användarinput medan uppgifter körs i bakgrunden. Å andra sidan, i de flesta fall är multithreading inte det enda sättet att hålla ett program responsivt. Detsamma kan uppnås genom asynkron I/O eller signaler i UNIX. [ett]
Operativsystem schemalägger trådar på ett av två sätt:
Fram till slutet av 1990-talet hade stationära processorer inte stöd för flera trådar, eftersom växling mellan trådar i allmänhet var långsammare än en kontextväxling för hela processen . Inbäddade processorer , som har högre krav på realtidsbeteende , kan stödja multithreading genom att minska tiden för att växla mellan trådar, kanske genom att allokera dedikerade registerfiler till varje exekveringstråd, istället för att spara/återställa en gemensam registerfil. I slutet av 1990-talet nådde idén om att köra instruktioner från flera trådar samtidigt, känd som simultan multithreading, kallad Hyper-Threading, stationära datorer med Intel Pentium 4-processorn . Sedan uteslöts den från Intel Core- och Core 2 -arkitekturprocessorerna , men återställdes senare i Core i7- arkitekturen .
Kritiker av multithreading hävdar att ökad användning av trådar har betydande nackdelar:
Även om exekveringstrådar verkar vara ett litet steg bort från sekventiell beräkning, är de i själva verket ett stort steg. De ger upp de viktigaste och mest attraktiva egenskaperna hos sekventiell beräkning: begriplighet, förutsägbarhet och determinism. Trådar av exekvering, som en modell för beräkning, är anmärkningsvärt icke-deterministiska, och att reducera denna icke-determinism blir programmerarens uppgift. [2]
Processen är den "tyngsta" kärnplaneringsenheten. Egna resurser för processen tilldelas av operativsystemet. Resurser inkluderar minne, filhandtag, sockets, enhetshandtag och fönster. Processer använder tidsdelningsadressutrymme och resursfiler endast genom explicita metoder som nedärvning av filbeskrivningar och delade minnessegment. Processer är vanligtvis förkonverterade till ett multitasking-läge för exekvering.
Kärntrådar är bland de "lätta" enheterna för kärnplanering. Inom varje process finns det minst en kärntråd för exekvering. Om flera kärnexekveringstrådar kan existera inom en process delar de samma minne och resursfil. Om exekveringsprocessen för schemaläggaren för operativsystemet är förgrundad, är kärntrådarna också multitasking i förgrunden. Kärntrådar har inga egna resurser, förutom anropsstacken , en kopia av processorns register , inklusive programräknaren , och trådens lokala minne (om sådant finns). Kärnan kan ange en exekveringstråd för varje logisk kärna i systemet (eftersom varje processor delar upp sig själv i flera logiska kärnor om den stöder multithreading, eller bara stöder en logisk kärna per fysisk kärna om den inte stöder multithreading), eller så kan den byt blockerade utförandetrådar. Emellertid tar kärntrådar mycket mer tid än det tar att byta användartrådar.
Exekveringstrådar implementeras ibland i bibliotekens användarutrymme , i vilket fall de kallas användartrådar för exekvering . Kärnan känner inte till dem, så de hanteras och schemaläggs i användarutrymmet. I vissa implementeringar är användartrådar för exekvering baserade på de översta kärntrådarna för exekvering för att dra fördel av multiprocessormaskiner (M:N-modeller). I den här artikeln hänvisar termen "tråd" som standard (utan "kärna" eller "anpassad" kvalificerare) till "kärntråd". Användartrådar för exekvering implementerade med hjälp av virtuella maskiner kallas också "gröna exekveringstrådar". Anpassade exekveringstrådar är i allmänhet snabba att skapa och enkla att hantera, men de kan inte dra fördel av multithreading och multiprocessing. De kan blockera om alla kärntrådar som är associerade med den är upptagna, även om vissa användartrådar är redo att köras.
Fibrer är ännu mer "lättvikts" schemaläggningsenheter relaterade till kooperativ multitasking : en körande fiber måste uttryckligen "ge" rätten att köra till andra fibrer, vilket gör implementeringen mycket enklare än att implementera kärntrådar eller användartrådar. Fibrer kan schemaläggas att köras på valfri exekveringstråd inom samma process. Detta tillåter applikationer att få prestandavinster genom att hantera sin egen schemaläggning, snarare än att förlita sig på kärnplaneraren (som kanske inte är konfigurerad för att göra det). Parallella programmeringsmiljöer som OpenMP implementerar vanligtvis sina uppgifter genom fibrer.
Trådar av körning i samma process delar samma adressutrymme. Detta gör att samtidigt exekverande koder kan kopplas tätt och bekvämt utbyta data utan överkostnaderna och komplexiteten för kommunikation mellan processer . När flera trådar delar ens enkla datastrukturer finns det risk för ett race-tillstånd om mer än en processorinstruktion krävs för att uppdatera data: två exekveringstrådar kan sluta med att försöka uppdatera datastrukturer samtidigt och sluta med data vars tillstånd skiljer sig från förväntat. Fel orsakade av rasförhållanden kan vara mycket svåra att reproducera och isolera.
För att undvika detta erbjuder trådar av exekveringsapplikationsprogrammeringsgränssnitt (API) synkroniseringsprimitiver , såsom mutexes , för att blockera datastrukturer från att nås samtidigt. På enprocessorsystem måste en tråd som kommer åt en låst mutex sluta köra och därför initiera en kontextväxling. På multiprocessorsystem kan en exekveringstråd få ett spinlock istället för att polla mutex . Båda dessa metoder kan försämra prestanda och tvinga processorn i SMP-system att konkurrera om minnesbussen, speciellt om nivån på låsmodularitet är för hög.
I/O och schemaläggningImplementeringen av anpassade trådar och fibrer görs vanligtvis helt i användarutrymmet. Som ett resultat är kontextväxling mellan användartrådar och fibrer i samma process mycket effektivt eftersom det inte kräver någon interaktion med kärnan alls. En kontextväxling utförs lokalt genom att spara processorregistren som används av en löpande användartråd eller fiber, och sedan ladda de register som krävs för ny exekvering. Eftersom schemaläggning sker i användarutrymmet kan schemaläggningspolicyn enkelt skräddarsys efter kraven för ett visst program.
Användningen av systemanropslås för användartrådar (i motsats till kärntrådar) och fibrer har dock sina egna problem. Om en användartråd eller fiber kör ett systemanrop kan inte andra trådar och fibrer i processen köras förrän den bearbetningen är klar. Ett typiskt exempel på ett sådant problem är relaterat till prestanda för I/O-operationer. De flesta program är designade för att utföra I/O synkront. När en I/O initieras görs ett systemanrop och det återkommer inte förrän det är slutfört. Under tiden blockeras hela processen av kärnan och kan inte köras, vilket gör andra användartrådar och fibrer i processen inoperable.
En vanlig lösning på detta problem är att tillhandahålla ett separat I/O API som implementerar ett synkront gränssnitt med intern icke-blockerande I/O, och starta en annan användartråd eller fiber medan I/O bearbetas. Liknande lösningar kan tillhandahållas för att blockera systemsamtal. Dessutom kan programmet skrivas för att undvika att använda synkrona I/O eller andra blockerande systemanrop.
SunOS 4.x introducerade så kallade " lättviktsprocesser " eller LWP . NetBSD 2.x + och DragonFly BSD implementerade LWP som kärntrådar (1:1-modell). SunOS 5.2 och upp till SunOS 5.8, och NetBSD 2 och upp till NetBSD 4, implementerade en tvåskiktsmodell med en eller flera användartrådar per kärntråd (M:N-modell). SunOS 5.9 och senare, och NetBSD 5, tog bort stödet för användartrådar och återgick till en 1:1-modell. [3] FreeBSD 5 implementerade M:N-modellen. FreeBSD 6 stöder både 1:1 och M:N-modeller, och användaren kan välja vilken som ska användas i ett givet program med /etc/libmap.conf. I FreeBSD version 7 har 1:1-modellen blivit standard, och i FreeBSD 8 och senare stöds inte M:N-modellen alls.
Att använda kärntrådar förenklar användarkoden genom att flytta några av de mer komplexa aspekterna av multithreading till kärnan. Programmet krävs inte för att schemalägga exekveringstrådar och explicita processorfångningar. Användarkod kan skrivas i den välbekanta procedurstilen, inklusive blockering av API-anrop, utan att neka andra trådar åtkomst till processorn. Emellertid kan kärntrådar orsaka en kontextväxling mellan exekveringstrådar när som helst och därigenom avslöja ras- och samtidighetsfel som kanske inte uppstår. På SMP-system är detta ännu mer förvärrat, eftersom kärntrådar bokstavligen kan köras samtidigt på olika processorer.
De körningstrådar som skapas av användaren i 1-1-modellen motsvarar sändbara kärnentiteter. Detta är den enklaste möjliga implementeringen av trådning. Windows API har tagit detta tillvägagångssätt från allra första början. På Linux implementerar det vanliga C-biblioteket detta tillvägagångssätt (genom POSIX Thread Library , och i äldre versioner, genom LinuxThreads ). Samma tillvägagångssätt används av Solaris OS , NetBSD och FreeBSD .
N:1-modellen antar att alla exekveringstrådar på användarnivå mappas till en enda schemaläggningsenhet på kärnnivå, och kärnan vet ingenting om sammansättningen av applikationstrådar. Med detta tillvägagångssätt kan kontextväxling göras mycket snabbt, och dessutom kan det implementeras även på enkla kärnor som inte stöder multithreading. En av dess främsta nackdelar är dock att den inte kan dra fördel av hårdvaruacceleration på flertrådade processorer eller multiprocessordatorer, eftersom endast en exekveringstråd kan schemaläggas vid varje given tidpunkt. Denna modell används i GNU Portable Threads.
I M:N-modellen mappas ett M antal applikationstrådar för exekvering till något N antal kärnentiteter eller "virtuella processorer". Modellen är en kompromiss mellan kärnnivåmodellen ("1:1") och användarnivåmodellen ("N:1"). Generellt sett är "M:N"-systemtrådning mer komplex att implementera än kärnan eller användartrådarna för exekvering eftersom inga kodändringar krävs för vare sig kärnan eller användarutrymmet. I en M:N-implementering är trådbiblioteket ansvarigt för att schemalägga användartrådar för exekvering på de tillgängliga schemaläggningsentiteterna. Samtidigt görs trådkontextväxling mycket snabbt, eftersom modellen undviker systemanrop. Emellertid ökar komplexiteten och sannolikheten för prioritetsinversioner, såväl som icke-optimal schemaläggning utan omfattande (och dyr) koordinering mellan användarschemaläggaren och kärnschemaläggaren.
Det finns många olika, inkompatibla implementeringar av strömmar. Dessa inkluderar både implementeringar på kärnnivå och implementeringar på användarnivå. Oftast följer de mer eller mindre nära POSIX Threads -gränssnittsstandarden .
Fibrer kan implementeras utan operativsystemstöd, även om vissa operativsystem och bibliotek tillhandahåller explicit stöd för dem.
Många programmeringsspråk stöder trådar på olika sätt. De flesta C- och C++-implementeringar (före C++11-standarden) ger inte direkt stöd för själva trådarna, men ger tillgång till trådar som tillhandahålls av operativsystem via ett API . Vissa högre nivåer (vanligtvis plattformsoberoende) programmeringsspråk, som Java , Python och .NET , tillhandahåller trådar till utvecklaren som en abstrakt, plattformsspecifik, distinkt från utvecklarens körtidsimplementering av trådar. Ett antal andra programmeringsspråk försöker också helt abstrahera konceptet med samtidighet och trådning från utvecklaren ( Cilk , OpenMP , MPI ...). Vissa språk är speciellt utformade för samtidighet (Ateji PX, CUDA ).
Vissa tolkande programmeringsspråk, som Ruby och CPython (en implementering av Python), stödjer trådar, men har en begränsning som kallas Global Interpreter Lock (GIL). GIL är en tolkexekverad undantagsmutex som kan förhindra tolken från att samtidigt tolka applikationskod i två eller flera trådar samtidigt, vilket effektivt begränsar samtidighet på flerkärniga system (främst för processorbundna trådar, inte nätverksbundna trådar) ). ).
av operativsystem | Aspekter|||||
---|---|---|---|---|---|
| |||||
Typer |
| ||||
Kärna |
| ||||
Processledning _ |
| ||||
Minneshantering och adressering | |||||
Ladda och initieringsverktyg | |||||
skal | |||||
Övrig | |||||
Kategori Wikimedia Commons Wikibooks Wiktionary |