Tråd (computing) - Thread (computing)

En proces med to tråde af udførelse, der kører på en processor

I datalogi , en tråd af udførelsen er den mindste sekvens af programmerede instruktioner, der kan styres uafhængigt af en scheduler , som typisk er en del af operativsystemet . Implementeringen af ​​tråde og processer er forskellig mellem operativsystemer, men i de fleste tilfælde er en tråd en del af en proces. De flere tråde i en given proces udføres muligvis samtidigt (via multithreading -funktioner) og deler ressourcer som f.eks. Hukommelse , mens forskellige processer ikke deler disse ressourcer. Især deler trådene i en proces dens eksekverbare kode og værdierne for dens dynamisk tildelte variabler og ikke- tråd-lokale globale variabler på et givet tidspunkt.

Historie

Tråde kom tidligt frem under navnet "opgaver" i OS/360 Multiprogrammering med et variabelt antal opgaver (MVT) i 1967. Saltzer (1966) tilskriver Victor A. Vyssotsky udtrykket "tråd".

Populariteten af ​​trådning er steget omkring 2003, da væksten i CPU -frekvensen blev erstattet med væksten i antallet af kerner, hvilket igen krævede samtidighed for at udnytte flere kerner.

Processer, kernetråde, brugertråde og fibre

Planlægning kan udføres på kerneniveau eller brugerniveau, og multitasking kan udføres præventivt eller i samarbejde. Dette giver en række relaterede begreber.

Processer

På kernelniveau indeholder en proces en eller flere kerneltråde , som deler processens ressourcer, såsom hukommelse og filhåndtag - en proces er en ressourceenhed, mens en tråd er en enhed for planlægning og udførelse. Kerneplanlægning udføres typisk ensartet præventivt eller, mindre almindeligt, kooperativt. På brugerniveau kan en proces som et runtime -system selv planlægge flere udførelsestråde. Hvis disse ikke deler data, som i Erlang, kaldes de normalt analogt for processer, mens hvis de deler data, kaldes de normalt (bruger) tråde , især hvis de er forudgående planlagt. Kooperativt planlagte brugertråde er kendt som fibre ; forskellige processer kan planlægge brugertråde forskelligt. Brugertråde kan udføres af kerneltråde på forskellige måder (en-til-en, mange-til-en, mange-til-mange). Udtrykket " letvægtsproces " refererer forskelligt til brugertråde eller til kernemekanismer til planlægning af brugertråde på kerneltråde.

En proces er en "tungvægtig" enhed for kerneplanlægning, da oprettelse, ødelæggelse og omskiftning af processer er relativt dyr. Behandler egne ressourcer, der er tildelt af operativsystemet. Ressourcer inkluderer hukommelse (for både kode og data), filhåndtag , stik, enhedshåndtag, vinduer og en processtyringsblok . Processer isoleres ved procesisolering og deler ikke adresserum eller filressourcer undtagen gennem eksplicitte metoder såsom arvelige filhåndtag eller delte hukommelsessegmenter eller kortlægning af den samme fil på en delt måde - se interprocesskommunikation . Oprettelse eller ødelæggelse af en proces er relativt dyr, da ressourcer skal erhverves eller frigives. Processer er typisk præemptivt multitaskede, og processkift er relativt dyrt, ud over grundlæggende omkostninger ved kontekstskift , på grund af problemer som cacheskylning (især processkift ændrer adressering af virtuel hukommelse, hvilket forårsager ugyldighed og dermed skylning af en umærket oversættelse af lookaside -buffer , især på x86).

Kerne tråde

En kerneltråd er en "let" enhed for kerneplanlægning. Der findes mindst en kernetråd inden for hver proces. Hvis der findes flere kernetråde inden for en proces, deler de den samme hukommelse og filressourcer. Kernel tråde forebyggende multitasked hvis operativsystemets proces scheduler er forebyggende. Kerneltråde ejer ikke ressourcer undtagen en stak , en kopi af registre inklusive programtælleren og trådlokal lagring (hvis nogen), og er dermed relativt billige at oprette og ødelægge. Trådskift er også relativt billigt: ​​det kræver en kontekstskift (gemmer og gendanner registre og stakmarkør), men ændrer ikke virtuel hukommelse og er dermed cache-venlig (efterlader TLB gyldig). Kernen kan tildele en tråd til hver logisk kerne i et system (fordi hver processor deler sig op i flere logiske kerner, hvis den understøtter multitrådning, eller kun understøtter en logisk kerne pr. Fysisk kerne, hvis den ikke gør det), og kan skifte tråde ud, der blive blokeret. Imidlertid tager kerneltråde meget længere tid end brugertråde at bytte.

Brugertråde

Tråde implementeres undertiden i userpace -biblioteker, der så kaldes brugertråde . Kernen er ikke klar over dem, så de administreres og planlægges i brugerområdet . Nogle implementeringer baserer deres brugertråde oven på flere kerneltråde for at drage fordel af maskiner med flere processorer ( M: N-model ). Brugertråde som implementeret af virtuelle maskiner kaldes også grønne tråde .

Da implementering af brugertråd typisk udelukkende er i brugerområdet , er kontekstskift mellem brugertråde inden for samme proces ekstremt effektivt, fordi det slet ikke kræver nogen interaktion med kernen: en kontekstskift kan udføres ved lokalt at gemme de CPU -registre, der bruges af i øjeblikket eksekverer bruger tråd eller fiber og derefter indlæser de registre, der kræves af den bruger tråd eller fiber, der skal eksekveres. Da planlægning forekommer i brugerområdet, kan planlægningspolitikken lettere tilpasses kravene til programmets arbejdsbyrde.

Imidlertid kan brugen af ​​blokering af systemopkald i brugertråde (i modsætning til kerneltråde) være problematisk. Hvis en brugertråd eller en fiber udfører et systemopkald, der blokerer, kan de andre tråde og fibre i processen ikke køre, før systemopkaldet vender tilbage. Et typisk eksempel på dette problem er ved udførelse af I/O: de fleste programmer er skrevet til at udføre I/O synkront. Når en I/O -operation startes, foretages et systemopkald og vender ikke tilbage, før I/O -operationen er afsluttet. I den mellemliggende periode "blokeres" hele processen af ​​kernen og kan ikke køre, hvilket sulter andre brugertråde og fibre i den samme proces fra at blive udført.

En fælles løsning på dette problem (bruges især af mange af implementeringer af grønne tråde) er at levere en I/O API, der implementerer en grænseflade, der blokerer den kaldende tråd, snarere end hele processen, ved hjælp af ikke-blokerende I/O internt og planlægning af en anden brugertråd eller fiber, mens I/O -operationen er i gang. Lignende løsninger kan leveres til andre blokerende systemopkald. Alternativt kan programmet skrives for at undgå brug af synkrone I/O eller andre blokerende systemopkald (især ved hjælp af ikke-blokerende I/O, herunder lambda-fortsættelser og/eller asynkronisering/ afventer primitiver).

Fibre

Fibre er en endnu lettere planlægningsenhed, der er planlagt i samarbejde : en kørende fiber skal eksplicit " give " for at tillade en anden fiber at køre, hvilket gør deres implementering meget lettere end kerne- eller brugertråde . En fiber kan planlægges at køre i en hvilken som helst tråd i samme proces. Dette giver applikationer mulighed for at opnå ydelsesforbedringer ved selv at administrere planlægningen i stedet for at stole på kerneplanlæggeren (som muligvis ikke er indstillet til applikationen). Parallelle programmeringsmiljøer som OpenMP implementerer typisk deres opgaver gennem fibre. Nær beslægtet med fibre er coroutiner , idet sondringen er, at coroutines er en konstruktion på sprogniveau, mens fibre er en konstruktion på systemniveau.

Tråde i forhold til processer

Tråde adskiller sig fra traditionelle multitasking operativsystemspecifikke processer på flere måder:

  • processer er typisk uafhængige, mens tråde findes som undersæt af en proces
  • processer bærer betydeligt mere statsinformation end tråde, hvorimod flere tråde inden for en proces deler processtatus samt hukommelse og andre ressourcer
  • processer har separate adresserum , mens tråde deler deres adresserum
  • processer interagerer kun gennem systemrelaterede billede inter-proces kommunikation mekanismer
  • kontekstskift mellem tråde i den samme proces sker typisk hurtigere end kontekstskift mellem processer

Systemer som Windows NT og OS/2 siges at have billige tråde og dyre processer; i andre operativsystemer er der ikke så stor forskel undtagen i omkostningerne ved en adresse-rum switch, hvilket på nogle arkitekturer (især x86 ) resulterer i en oversættelse lookaside buffer (TLB) flush.

Fordele og ulemper ved tråde vs processer inkluderer:

  • Lavere ressourceforbrug af tråde: ved hjælp af tråde kan et program fungere ved hjælp af færre ressourcer, end det ville have brug for, når der bruges flere processer.
  • Forenklet deling og kommunikation af tråde: I modsætning til processer, der kræver en meddelelsesoverførsel eller delt hukommelsesmekanisme for at udføre interprocesskommunikation (IPC), kan tråde kommunikere gennem data, kode og filer, de allerede deler.
  • Tråd krasjer en proces : På grund af tråde, der deler det samme adresserum, kan en ulovlig handling udført af en tråd gå ned i hele processen; derfor kan en misforstået tråd forstyrre behandlingen af ​​alle de andre tråde i applikationen.

Planlægning

Forebyggende i forhold til kooperativ planlægning

Operativsystemer planlægger tråde enten præventivt eller samarbejde . Flerbrugeroperativsystemer favoriserer generelt præventiv multithreading for sin finere kontrol over udførelsestiden via kontekstskift . Imidlertid kan præventiv planlægning kontekstskifte tråde i øjeblikke, som programmerere ikke har forventet, hvilket kan forårsage låsekonvoj , prioriteret inversion eller andre bivirkninger. I modsætning hertil er kooperativ multithreading afhængig af tråde for at opgive kontrollen med udførelsen og dermed sikre, at tråde kører til færdiggørelse . Dette kan forårsage problemer, hvis en koordineret multitasket tråd blokerer ved at vente på en ressource, eller hvis den sulter andre tråde ved ikke at give kontrol over udførelsen under intensiv beregning.

Enkelt over for multi-processor systemer

Indtil begyndelsen af ​​2000'erne havde de fleste stationære computere kun en enkelt-core CPU uden understøttelse af hardware-tråde , selvom tråde stadig blev brugt på sådanne computere, fordi skift mellem tråde generelt stadig var hurtigere end fuld-proces kontekst switches . I 2002 tilføjede Intel understøttelse af samtidig multithreading til Pentium 4- processoren under navnet hyper-threading ; i 2005 introducerede de dual-core Pentium D- processoren og AMD introducerede dual-core Athlon 64 X2- processoren.

Systemer med en enkelt processor implementerer generelt multithreading ved tidsskæring : den centrale processorenhed (CPU) skifter mellem forskellige softwaretråde . Denne kontekstskiftning sker normalt ofte nok til, at brugerne opfatter trådene eller opgaverne som kørende parallelt (for populære server-/desktop-operativsystemer er maksimal tidsskive af en tråd, når andre tråde venter, ofte begrænset til 100-200ms). På en multiprocessor eller multi-core system kan flere tråde udføres parallelt , idet hver processor eller kerne udfører en separat tråd samtidigt; på en processor eller kerne med hardwaretråde kan separate softwaretråde også udføres samtidigt med separate hardwaretråde.

Gevindmodeller

1: 1 (trådning på kerneliveau)

Tråde oprettet af brugeren i en 1: 1 -korrespondance med planlagte enheder i kernen er den enkleste mulige implementering af tråde. OS/2 og Win32 brugte denne tilgang fra starten, mens det på Linux implementerer det sædvanlige C -bibliotek denne tilgang (via NPTL eller ældre LinuxThreads ). Denne fremgangsmåde bruges også af Solaris , NetBSD , FreeBSD , macOS og iOS .

N : 1 (trådning på brugerniveau)

En N : 1-model indebærer, at alle tråde på applikationsniveau tilknyttes en planlagt enhed på kernelniveau; kernen har ikke kendskab til applikationstrådene. Med denne tilgang kan kontekstskift udføres meget hurtigt, og derudover kan det implementeres selv på enkle kerner, der ikke understøtter trådning. En af de største ulemper er imidlertid, at den ikke kan drage fordel af hardware-accelerationen på multitrådede processorer eller computere med flere processorer : der planlægges aldrig mere end en tråd på samme tid. For eksempel: Hvis en af ​​trådene skal udføre en I/O -anmodning, blokeres hele processen, og gevindforslaget kan ikke bruges. De GNU Portable Threads bruger Bruger-niveau gevindskæring, som gør statslige Tråde .

M : N (hybrid gevind)

M : N tilknytter nogle M -antal applikationstråde til nogle N -antal kerneenheder eller "virtuelle processorer". Dette er et kompromis mellem kernel-niveau ("1: 1") og bruger-niveau (" N : 1") trådning. Generelt er " M : N " gevindsystemer mere komplekse at implementere end enten kerne- eller brugertråde, fordi ændringer af både kerne- og brugerrumskode er påkrævede. I M: N -implementeringen er threading -biblioteket ansvarligt for at planlægge brugertråde på de tilgængelige planlagte enheder; dette gør kontekstskift af tråde meget hurtigt, da det undgår systemopkald. Dette øger imidlertid kompleksiteten og sandsynligheden for prioriteret inversion samt suboptimal planlægning uden omfattende (og dyr) koordinering mellem userland -planlæggeren og kerneplanlægningen.

Hybridimplementeringseksempler

Historik om gevindmodeller i Unix -systemer

SunOS 4.x implementerede lette processer eller LWP'er. NetBSD 2.x+og DragonFly BSD implementerer LWP'er som kerneltråde (1: 1 model). SunOS 5.2 til og med SunOS 5.8 samt NetBSD 2 til NetBSD 4 implementerede en to -niveau model, der multipleksede en eller flere tråde på brugerniveau på hver kernetråd (M: N -model). SunOS 5.9 og nyere, samt NetBSD 5 eliminerede understøttelse af brugertråde og vendte tilbage til en 1: 1 -model. FreeBSD 5 implementeret M: N model. FreeBSD 6 understøttede både 1: 1 og M: N, brugere kunne vælge, hvilket program der skulle bruges med et givet program ved hjælp af /etc/libmap.conf. Fra og med FreeBSD 7 blev 1: 1 standard. FreeBSD 8 understøtter ikke længere M: N -modellen.

Single-threaded vs multithreaded-programmer

I computerprogrammering er single-threading behandlingen af ​​en kommando ad gangen. I den formelle analyse af variablenes semantik og procestilstand kan udtrykket single threading bruges forskelligt til at betyde "backtracking inden for en enkelt tråd", hvilket er almindeligt i det funktionelle programmeringsfællesskab .

Multithreading findes hovedsageligt i multitasking -operativsystemer. Multithreading er en udbredt programmerings- og udførelsesmodel, der tillader flere tråde at eksistere inden for en proces. Disse tråde deler processens ressourcer, men er i stand til at udføre uafhængigt. Den trådede programmeringsmodel giver udviklere en nyttig abstraktion af samtidig udførelse. Multithreading kan også anvendes på en proces for at muliggøre parallel udførelse på et multiprocessingssystem .

Multithreading -biblioteker har en tendens til at levere et funktionsopkald for at oprette en ny tråd, som tager en funktion som parameter. En samtidig tråd oprettes derefter, som begynder at køre den beståede funktion og slutter, når funktionen vender tilbage. Trådbibliotekerne tilbyder også datasynkroniseringsfunktioner.

Tråde og datasynkronisering

Tråde i den samme proces deler det samme adresserum. Dette tillader samtidig kørende kode at koble tæt og bekvemt udveksling af data uden omkostninger eller kompleksitet ved en IPC . Når de deles mellem tråde, bliver selv simple datastrukturer imidlertid tilbøjelige til raceforhold, hvis de kræver mere end én CPU -instruktion for at opdatere: to tråde kan ende med at forsøge at opdatere datastrukturen på samme tid og finde det uventet at ændre under fødderne. Fejl forårsaget af raceforhold kan være meget vanskelige at reproducere og isolere.

For at forhindre dette tilbyder threading applikationsprogrammeringsgrænseflader (API'er) synkroniseringsprimitiver såsom mutexes for at låse datastrukturer mod samtidig adgang. På uniprocessorsystemer skal en tråd, der løber ind i en låst mutex, sove og dermed udløse en kontekst switch. På systemer med flere processorer kan tråden i stedet polle mutexen i en spinlock . Begge disse kan ødelægge ydeevne og tvinge processorer i symmetriske multiprocessingsystemer (SMP) til at konkurrere om hukommelsesbussen, især hvis granuliteten af låsen er for fin.

Andre synkroniserings -API'er omfatter tilstandsvariabler , kritiske sektioner , semaforer og skærme .

Trådbassiner

Et populært programmeringsmønster, der involverer tråde, er det i trådpuljer, hvor der oprettes et bestemt antal tråde ved opstart, der derefter venter på, at en opgave skal tildeles. Når en ny opgave ankommer, vågner den, fuldfører opgaven og venter tilbage. Dette undgår de relativt dyre trådoprettelses- og ødelæggelsesfunktioner for hver udført opgave og tager trådstyring ud af applikationsudviklerens hånd og overlader det til et bibliotek eller et operativsystem, der er bedre egnet til at optimere trådstyring.

Multithreaded programmer vs single-threaded programmer fordele og ulemper

Multithreaded applikationer har følgende fordele vs single-threaded dem:

  • Lydhørhed : multithreading kan gøre det muligt for en applikation at være lydhør over for input. I et etrådsprogram, hvis hovedudførelsestråden blokerer for en langvarig opgave, kan hele applikationen se ud til at fryse. Ved at flytte sådanne langvarige opgaver til en arbejdstråd, der kører samtidigt med hovedudførelsestråden, er det muligt for applikationen at forblive lydhør over for brugerinput, mens der udføres opgaver i baggrunden. På den anden side er multithreading i de fleste tilfælde ikke den eneste måde at holde et program lydhørt, idet ikke-blokerende I/O- og/eller Unix-signaler er tilgængelige for at opnå lignende resultater.
  • Parallelisering : applikationer, der ønsker at bruge multicore- eller multi-CPU-systemer, kan bruge multithreading til at opdele data og opgaver i parallelle delopgaver og lade den underliggende arkitektur styre, hvordan trådene kører, enten samtidigt på en kerne eller parallelt på flere kerner. GPU -computermiljøer som CUDA og OpenCL bruger multithreading -modellen, hvor snesevis til hundredvis af tråde kører parallelt på tværs af data på et stort antal kerner . Dette muliggør igen bedre systemudnyttelse, og (forudsat at synkroniseringsomkostninger ikke spiser fordelene op), kan give hurtigere programkørsel.

Multithreaded applikationer har følgende ulemper:

  • Synkroniseringskompleksitet og relaterede fejl: Når der bruges delte ressourcer, der er typiske for gevindprogrammer, skal programmøren være forsigtig med at undgå løbebetingelser og anden ikke-intuitiv adfærd. For at data kan manipuleres korrekt, vil tråde ofte skulle mødes i tide for at behandle dataene i den korrekte rækkefølge. Tråde kan også kræve gensidigt eksklusive operationer (ofte implementeret ved hjælp af mutexes ) for at forhindre, at fælles data læses eller overskrives i en tråd, mens de ændres af en anden. Skødesløs brug af sådanne primitiver kan føre til Blokeret låsestilling , livelocks eller racer om ressourcer. Som Edward A. Lee har skrevet: "Selvom tråde ser ud til at være et lille skridt fra sekventiel beregning, repræsenterer de faktisk et stort skridt. De kasserer de mest essentielle og tiltalende egenskaber ved sekventiel beregning: forståelighed, forudsigelighed og determinisme. Tråde , som en beregningsmodel, er vildt ikke-deterministiske, og programmørens job bliver en beskæring af den ubestemthed. "
  • At være utestabil . Generelt er multithreaded-programmer ikke-deterministiske og er derfor ikke-testbare. Med andre ord kan et multitrådsprogram let have fejl, som aldrig manifesterer sig på et testsystem, kun manifesteret i produktionen. Dette kan afhjælpes ved at begrænse kommunikation mellem tråde til bestemte veldefinerede mønstre (f.eks. Meddelelsesoverførsel).
  • Synkroniseringsomkostninger . Da trådkontekstskift på moderne CPU'er kan koste op til 1 million CPU -cykler, gør det det svært at skrive effektive multithreading -programmer. Især skal der lægges særlig vægt på at undgå, at synkronisering mellem tråde bliver for hyppig.

Understøttelse af programmeringssprog

Mange programmeringssprog understøtter trådning i en vis kapacitet.

  • IBM PL/I (F) inkluderede understøttelse af multithreading (kaldet multitasking ) allerede i slutningen af ​​1960'erne, og dette blev fortsat i Optimizing Compiler og senere versioner. IBM Enterprise PL/I -kompilatoren introducerede en ny model "thread" API. Ingen af ​​versionerne var en del af PL/I -standarden.
  • Mange implementeringer af C og C ++ understøtter threading og giver adgang til operativsystemets native threading API'er. En standardiseret grænseflade til implementering af tråde er POSIX Threads (Pthreads), som er et sæt C-funktionsbibliotekskald. OS -leverandører kan frit implementere grænsefladen som ønsket, men applikationsudvikleren skal kunne bruge den samme grænseflade på tværs af flere platforme. De fleste Unix -platforme inklusive Linux understøtter Pthreads. Microsoft Windows har sit eget sæt trådfunktioner i process.h -grænsefladen til multithreading, som startthread .
  • Nogle programmeringssprog på et højere niveau (og normalt på tværs af platforme ), f.eks. Java- , Python- og .NET Framework- sprog, udsætter threading for udviklere, mens de abstraherer platformens specifikke forskelle i threading-implementeringer i runtime. Flere andre programmeringssprog og sprogudvidelser forsøger også at abstrahere begrebet samtidighed og trådning helt fra udvikleren ( Cilk , OpenMP , Message Passing Interface (MPI)). Nogle sprog er designet til sekventiel parallelisme i stedet (især ved hjælp af GPU'er), uden at det kræver samtidighed eller tråde ( Ateji PX , CUDA ).
  • Et par fortolkede programmeringssprog har implementeringer (f.eks. Ruby MRI for Ruby, CPython for Python), der understøtter trådning og samtidighed, men ikke parallel udførelse af tråde, på grund af en global tolkelås (GIL). GIL er en gensidig eksklusionslås, der er i besiddelse af tolken, og som kan forhindre tolken i at fortolke applikationskoden samtidigt på to eller flere tråde på én gang, hvilket effektivt begrænser parallelismen på flere kernesystemer. Dette begrænser ydeevnen mest for processorbundne tråde, som kræver processoren, og ikke meget for I/O-bundne eller netværksbundne. Andre implementeringer af fortolkede programmeringssprog, såsom Tcl ved hjælp af Thread -udvidelsen, undgår GIL -grænsen ved at bruge en lejlighedsmodel, hvor data og kode eksplicit skal "deles" mellem tråde. I Tcl har hver tråd en eller flere tolke.
  • I programmeringsmodeller som CUDA designet til dataparallel beregning kører en række tråde den samme kode parallelt ved kun at bruge dens ID til at finde sine data i hukommelsen. I det væsentlige skal applikationen være designet, så hver tråd udfører den samme operation på forskellige hukommelsessegmenter, så de kan fungere parallelt og bruge GPU -arkitekturen.
  • Hardwarebeskrivelsessprog som Verilog har en anden gevindmodel, der understøtter ekstremt stort antal tråde (til modellering af hardware).

Se også

Referencer

Yderligere læsning

  • David R. Butenhof: Programmering med POSIX-tråde , Addison-Wesley, ISBN  0-201-63392-2
  • Bradford Nichols, Dick Buttlar, Jacqueline Proulx Farell: Pthreads Programming , O'Reilly & Associates, ISBN  1-56592-115-1
  • Paul Hyde: Java Thread Programming , Sams, ISBN  0-672-31585-8
  • Jim Beveridge, Robert Wiener: Multithreading-applikationer i Win32 , Addison-Wesley, ISBN  0-201-44234-5
  • Uresh Vahalia: Unix Internals: The New Frontiers , Prentice Hall, ISBN  0-13-101908-2