Python-programmeringsspråket lar deg bruke multibearbeiding eller multitråding. I denne opplæringen lærer du hvordan du skriver flertrådede applikasjoner i Python.
Hva er en tråd?
En tråd er en ekseksjonsenhet ved samtidig programmering. Multithreading er en teknikk som gjør at en CPU kan utføre mange oppgaver i en prosess samtidig. Disse trådene kan utføres hver for seg mens de deler prosessressursene sine.
Hva er en prosess?
En prosess er i utgangspunktet programmet i utførelse. Når du starter et program på datamaskinen din (som en nettleser eller tekstredigerer), lager operativsystemet en prosess.
Hva er multithreading i Python?
Multitrading i Python- programmering er en kjent teknikk der flere tråder i en prosess deler datarommet med hovedtråden som gjør informasjonsdeling og kommunikasjon i tråder enkelt og effektivt. Trådene er lettere enn prosesser. Flere tråder kan utføres hver for seg mens de deler prosessressursene sine. Hensikten med multithreading er å kjøre flere oppgaver og fungere celler samtidig.
Hva er multiprosessering?
Multiprosessering lar deg kjøre flere ikke-relaterte prosesser samtidig. Disse prosessene deler ikke ressursene sine og kommuniserer gjennom IPC.
Python Multithreading vs Multiprocessing
For å forstå prosesser og tråder, bør du vurdere dette scenariet: En .exe-fil på datamaskinen din er et program. Når du åpner den, laster operativsystemet den inn i minnet, og CPUen utfører den. Forekomsten av programmet som nå kjører kalles prosessen.
Hver prosess vil ha to grunnleggende komponenter:
- Koden
- Dataen
Nå kan en prosess inneholde en eller flere underdeler som kalles tråder. Dette avhenger av OS-arkitekturen. Du kan tenke på en tråd som en del av prosessen som kan utføres separat av operativsystemet.
Med andre ord er det en strøm av instruksjoner som kan kjøres uavhengig av operativsystemet. Tråder i en enkelt prosess deler dataene fra prosessen og er designet for å samarbeide for å legge til rette for parallellitet.
I denne veiledningen vil du lære,
- Hva er en tråd?
- Hva er en prosess?
- Hva er Multithreading?
- Hva er multiprosessering?
- Python Multithreading vs Multiprocessing
- Hvorfor bruke Multithreading?
- Python MultiThreading
- Tråd- og trådmodulene
- Trådmodulen
- Gjengemodulen
- Blokkeringer og løpsforhold
- Synkroniserer tråder
- Hva er GIL?
- Hvorfor var GIL nødvendig?
Hvorfor bruke Multithreading?
Multithreading lar deg dele opp et program i flere underoppgaver og kjøre disse oppgavene samtidig. Hvis du bruker multitrading riktig, kan applikasjonshastighet, ytelse og gjengivelse forbedres.
Python MultiThreading
Python støtter konstruksjoner for både multiprosessering og multithreading. I denne opplæringen vil du primært fokusere på å implementere flertrådede applikasjoner med python. Det er to hovedmoduler som kan brukes til å håndtere tråder i Python:
- Den tråd modul, og
- Den gjenging Modulen
Imidlertid er det i python også noe som kalles en global tolkelås (GIL). Det tillater ikke mye ytelsesgevinst og kan til og med redusere ytelsen til noen flertrådede applikasjoner. Du vil lære alt om det i de kommende delene av denne opplæringen.
Tråd- og trådmodulene
De to modulene du vil lære om i denne opplæringen, er trådmodulen og trådmodulen .
Trådmodulen har imidlertid lenge blitt avviklet. Fra og med Python 3 har den blitt betegnet som foreldet og er bare tilgjengelig som __thread for bakoverkompatibilitet.
Du bør bruke den overordnede threading modul for programmer som du har tenkt å distribuere. Trådmodulen er bare dekket her for pedagogiske formål.
Trådmodulen
Syntaksen for å lage en ny tråd ved hjelp av denne modulen er som følger:
thread.start_new_thread(function_name, arguments)
OK, nå har du dekket den grunnleggende teorien for å starte koding. Så åpne din IDLE eller en notisblokk og skriv inn følgende:
import timeimport _threaddef thread_test(name, wait):i = 0while i <= 3:time.sleep(wait)print("Running %s\n" %name)i = i + 1print("%s has finished execution" %name)if __name__ == "__main__":_thread.start_new_thread(thread_test, ("First Thread", 1))_thread.start_new_thread(thread_test, ("Second Thread", 2))_thread.start_new_thread(thread_test, ("Third Thread", 3))
Lagre filen og trykk F5 for å kjøre programmet. Hvis alt ble gjort riktig, er dette resultatet du bør se:
Du vil lære mer om løpsforhold og hvordan du håndterer dem i de kommende seksjonene
KODEKLARING
- Disse uttalelsene importerer tids- og trådmodulen som brukes til å håndtere utførelse og forsinkelse av Python-trådene.
- Her har du definert en funksjon som heter thread_test, som vil bli kalt etter metoden start_new_thread . Funksjonen kjører en stundsløyfe for fire iterasjoner og skriver ut navnet på tråden som kalte den. Når iterasjonen er fullført, skriver den ut en melding som sier at tråden er fullført.
- Dette er hoveddelen av programmet ditt. Her kaller du ganske enkelt start_new_thread- metoden med thread_test- funksjonen som argument.
Dette vil opprette en ny tråd for funksjonen du sender som argument og begynne å utføre den. Merk at du kan erstatte denne (thread _ test) med en hvilken som helst annen funksjon du vil kjøre som en thread.
Gjengemodulen
Denne modulen er implementering på høyt nivå av threading i python og de facto-standarden for administrering av flertrådede applikasjoner. Det gir et bredt spekter av funksjoner sammenlignet med trådmodulen.

Her er en liste over noen nyttige funksjoner definert i denne modulen:
Funksjonsnavn | Beskrivelse |
activeCount () | Returnerer antall trådobjekter som fremdeles lever |
gjeldende tråd () | Returnerer gjeldende objekt for trådklassen. |
oppregne () | Viser alle aktive trådobjekter. |
isDaemon () | Returnerer sant hvis tråden er en demon. |
er i live() | Returnerer sant hvis tråden fremdeles er i live. |
Trådklassemetoder | |
start() | Starter aktiviteten til en tråd. Det må bare kalles en gang for hver tråd, fordi det vil kaste en kjøretidsfeil hvis den blir kalt flere ganger. |
løpe() | Denne metoden angir aktiviteten til en tråd og kan overstyres av en klasse som utvider trådklassen. |
bli med() | Det blokkerer kjøringen av annen kode til tråden som join () -metoden ble kalt, avsluttes. |
Backstory: The Thread Class
Før du begynner å kode flertrådede programmer ved hjelp av trådmodulen, er det viktig å forstå trådklassen. Trådklassen er den primære klassen som definerer malen og operasjonene til en tråd i python.
Den vanligste måten å lage et flertrådet pythonapplikasjon på er å erklære en klasse som utvider trådklassen og overstyrer den kjørte () metoden.
Trådklassen, i sammendrag, betyr en kodesekvens som kjører i en egen tråd for kontroll.
Så når du skriver en app med flere tråder, vil du gjøre følgende:
- definere en klasse som utvider trådklassen
- Overstyr __init__- konstruktøren
- Overstyr run () -metoden
Når et trådobjekt er laget, kan start () -metoden brukes til å begynne utførelsen av denne aktiviteten, og join () -metoden kan brukes til å blokkere all annen kode til den nåværende aktiviteten er ferdig.
La oss nå prøve å bruke trådmodulen til å implementere ditt forrige eksempel. Igjen, fyr opp IDLE og skriv inn følgende:
import timeimport threadingclass threadtester (threading.Thread):def __init__(self, id, name, i):threading.Thread.__init__(self)self.id = idself.name = nameself.i = idef run(self):thread_test(self.name, self.i, 5)print ("%s has finished execution " %self.name)def thread_test(name, wait, i):while i:time.sleep(wait)print ("Running %s \n" %name)i = i - 1if __name__=="__main__":thread1 = threadtester(1, "First Thread", 1)thread2 = threadtester(2, "Second Thread", 2)thread3 = threadtester(3, "Third Thread", 3)thread1.start()thread2.start()thread3.start()thread1.join()thread2.join()thread3.join()
Dette vil være utdata når du utfører koden ovenfor:
KODEKLARING
- Denne delen er den samme som vårt forrige eksempel. Her importerer du tids- og trådmodulen som brukes til å håndtere kjøringen og forsinkelsene av Python-trådene.
- I denne biten, er du opprette en klasse kalt threadtester, som arver eller utvider tråden klassen av threading modulen. Dette er en av de vanligste måtene å lage tråder i python. Du bør imidlertid bare overstyre konstruktøren og run () -metoden i appen din. Som du kan se i ovennevnte kodeeksempel, er __init__- metoden (konstruktør) overstyrt.
På samme måte har du også overstyrt run () -metoden. Den inneholder koden du vil utføre i en tråd. I dette eksemplet har du kalt thread_test () -funksjonen.
- Dette er thread_test () -metoden som tar verdien av i som et argument, reduserer den med 1 ved hver iterasjon og løper gjennom resten av koden til jeg blir 0. I hver iterasjon skriver den ut navnet på den gjeldende kjøringen og sover i ventesekunder (som også tas som et argument).
- thread1 = threadtester (1, "First Thread", 1)
Her lager vi en tråd og sender de tre parametrene som vi erklærte i __init__. Den første parameteren er trådens id, den andre parameteren er trådens navn, og den tredje parameteren er telleren, som bestemmer hvor mange ganger mens løkken skal kjøres.
- thread2.start ()
Startmetoden brukes til å starte kjøringen av en tråd. Internt kaller start () -funksjonen run () -metoden i klassen din.
- thread3.join ()
Metoden join () blokkerer kjøringen av annen kode og venter til tråden den ble kalt på avsluttes.
Som du allerede vet, har trådene som er i samme prosess tilgang til minnet og dataene til den prosessen. Som et resultat, hvis mer enn en tråd prøver å endre eller få tilgang til dataene samtidig, kan feil krype inn.
I neste avsnitt vil du se de forskjellige typer komplikasjoner som kan vises når tråder får tilgang til data og kritisk seksjon uten å sjekke for eksisterende tilgangstransaksjoner.
Blokkeringer og løpsforhold
Før du lærer om fastlåsning og løpsforhold, vil det være nyttig å forstå noen grunnleggende definisjoner relatert til samtidig programmering:
- Kritisk seksjon
Det er et fragment av kode som får tilgang til eller endrer delte variabler og må utføres som en atomtransaksjon.
- Kontekstbryter
Det er prosessen som en CPU følger for å lagre tilstanden til en tråd før du bytter fra en oppgave til en annen slik at den kan gjenopptas fra samme punkt senere.
Låsesperre
Dokkelåser er det mest fryktede problemet utviklere møter når de skriver samtidige / flertrådede applikasjoner i python. Den beste måten å forstå fastlåsning er ved å bruke det klassiske datavitenskapelige eksempelproblemet kjent som Dining Philosophers Problem.
Problemstillingen for serveringsfilosofer er som følger:
Fem filosofer sitter på et rundt bord med fem plater spaghetti (en type pasta) og fem gafler, som vist i diagrammet.

Til enhver tid må en filosof enten spise eller tenke.
Videre må en filosof ta de to gaflene ved siden av seg (dvs. venstre og høyre gafler) før han kan spise spagettien. Problemet med fastlåst oppstår når alle fem filosofer plukker opp høyre gafler samtidig.
Siden hver av filosofene har en gaffel, vil de alle vente på at de andre legger gaffelen. Som et resultat vil ingen av dem kunne spise spaghetti.
Tilsvarende, i et samtidig system, oppstår en fastlåst situasjon når forskjellige tråder eller prosesser (filosofer) prøver å skaffe seg delte systemressurser (gafler) samtidig. Som et resultat får ingen av prosessene sjansen til å utføre ettersom de venter på en annen ressurs som ligger i en annen prosess.
Løpsforhold
En løpetilstand er en uønsket tilstand av et program som oppstår når et system utfører to eller flere operasjoner samtidig. Tenk for eksempel på dette enkelt for loop:
i=0; # a global variablefor x in range(100):print(i)i+=1;
Hvis du oppretter n antall tråder som kjører denne koden på en gang, kan du ikke bestemme verdien på i (som deles av trådene) når programmet er ferdig med kjøringen. Dette er fordi i et ekte flertrådingsmiljø kan trådene overlappe hverandre, og verdien av i som ble hentet og endret av en tråd kan endre seg mellom når en annen tråd får tilgang til den.
Dette er de to hovedklassene av problemer som kan oppstå i en flertrådet eller distribuert pythonapplikasjon. I neste avsnitt vil du lære hvordan du kan løse dette problemet ved å synkronisere tråder.
Synkroniserer tråder
For å håndtere løpsforhold, fastlåsning og andre trådbaserte problemer, gir threading-modulen Lock- objektet. Tanken er at når en tråd vil ha tilgang til en bestemt ressurs, får den en lås for den ressursen. Når en tråd låser en bestemt ressurs, kan ingen annen tråd få tilgang til den før låsen frigjøres. Som et resultat vil endringene i ressursen være atomare, og raseforholdene vil bli avverget.
En lås er en primitiv synkroniseringsprimitiv implementert av __thread- modulen. Når som helst kan en lås være i en av to tilstander: låst eller ulåst. Den støtter to metoder:
- tilegne()
Når låsetilstanden er låst opp, vil anropet oppkjøpe () -metoden endre tilstanden til låst og returnere. Imidlertid, Hvis staten er låst, blir anropet om å anskaffe () sperret til frigjøringsmetoden () kalles av en annen tråd.
- utgivelse()
Release () -metoden brukes til å stille tilstanden til ulåst, dvs. frigjøre en lås. Det kan kalles av hvilken som helst tråd, ikke nødvendigvis den som fikk låsen.
Her er et eksempel på bruk av låser i appene dine. Avfyr IDLE og skriv inn følgende:
import threadinglock = threading.Lock()def first_function():for i in range(5):lock.acquire()print ('lock acquired')print ('Executing the first funcion')lock.release()def second_function():for i in range(5):lock.acquire()print ('lock acquired')print ('Executing the second funcion')lock.release()if __name__=="__main__":thread_one = threading.Thread(target=first_function)thread_two = threading.Thread(target=second_function)thread_one.start()thread_two.start()thread_one.join()thread_two.join()
Nå, trykk på F5. Du bør se en utgang som dette:
KODEKLARING
- Her oppretter du ganske enkelt en ny lås ved å ringe threading.Lock () fabrikkfunksjonen. Internt returnerer Lock () en forekomst av den mest effektive betonglåseklassen som vedlikeholdes av plattformen.
- I den første uttalelsen anskaffer du låsen ved å ringe anskaffe () metoden. Når låsen er gitt, skriver du ut "lås ervervet" til konsollen. Når all koden du vil at tråden skal kjøre, er fullført, frigjør du låsen ved å ringe frigjøringsmetoden ().
Teorien er bra, men hvordan vet du at låsen virkelig fungerte? Hvis du ser på utdataene, vil du se at hvert av utskriftsuttalelsene skriver ut nøyaktig en linje om gangen. Husk at i et tidligere eksempel var utgangene fra utskrift tilfeldig fordi flere tråder hadde tilgang til utskriftsmetoden () samtidig. Her kalles utskriftsfunksjonen først etter at låsen er anskaffet. Så, utgangene vises en om gangen og linje for linje.
Bortsett fra låser, støtter python også noen andre mekanismer for å håndtere trådsynkronisering som listet nedenfor:
- RLocks
- Semaforer
- Forhold
- Arrangementer og
- Barrierer
Global tolkelås (og hvordan du skal håndtere det)
Før vi går inn i detaljene i pythons GIL, la oss definere noen få begreper som vil være nyttige for å forstå den kommende delen:
- CPU-bundet kode: dette refererer til en hvilken som helst kode som vil bli utført direkte av CPUen.
- I / O-bundet kode: dette kan være hvilken som helst kode som får tilgang til filsystemet gjennom operativsystemet
- CPython: det er referansen implementeringen av Python og kan beskrives som tolk skrevet i C og Python (programmeringsspråk).
Hva er GIL i Python?
Global Interpreter Lock (GIL) i python er en prosesslås eller en mutex som brukes når du arbeider med prosessene. Det sørger for at en tråd kan få tilgang til en bestemt ressurs om gangen, og det forhindrer også bruk av objekter og bytekoder samtidig. Dette gagner programmene med enkelt tråd i en ytelsesøkning. GIL i python er veldig enkelt og enkelt å implementere.
En lås kan brukes til å sikre at bare en tråd har tilgang til en bestemt ressurs på et gitt tidspunkt.
En av funksjonene til Python er at den bruker en global lås på hver tolkeprosess, noe som betyr at hver prosess behandler selve pythontolken som en ressurs.
Anta for eksempel at du har skrevet et pythonprogram som bruker to tråder for å utføre både CPU- og 'I / O' -operasjoner. Når du kjører dette programmet, er det dette som skjer:
- Pythontolken lager en ny prosess og gyter trådene
- Når tråd-1 begynner å kjøre, vil den først skaffe seg GIL og låse den.
- Hvis tråd-2 ønsker å utføre nå, må den vente på at GIL skal slippes selv om en annen prosessor er gratis.
- Anta nå at tråd-1 venter på en I / O-operasjon. På dette tidspunktet vil den frigjøre GIL, og thread-2 vil skaffe seg den.
- Etter at I / O-opsjonene er fullført, hvis tråd-1 ønsker å utføre nå, må den igjen vente på at GIL skal frigjøres av tråd-2.
På grunn av dette kan bare en tråd få tilgang til tolken når som helst, noe som betyr at det bare vil være en tråd som utfører python-kode på et gitt tidspunkt.
Dette er greit i en enkeltkjerneprosessor fordi den bruker tidsskjæring (se den første delen av denne opplæringen) for å håndtere trådene. Imidlertid, i tilfelle multikjerneprosessorer, vil en CPU-bundet funksjon som kjøres på flere tråder, ha betydelig innvirkning på programmets effektivitet, siden den faktisk ikke vil bruke alle tilgjengelige kjerner samtidig.
Hvorfor var GIL nødvendig?
CPython søppeloppsamler bruker en effektiv minnestyringsteknikk kjent som referansetelling. Slik fungerer det: Hvert objekt i python har et referansetall, som økes når det tildeles et nytt variabelnavn eller legges til en container (som tupler, lister osv.). På samme måte reduseres referansetellingen når referansen går utenfor omfanget eller når del-setningen kalles. Når referansetellingen til et objekt når 0, samles det søppel, og det tildelte minnet frigjøres.
Men problemet er at referansetallvariabelen er utsatt for raseforhold som alle andre globale variabler. For å løse dette problemet bestemte utviklerne av python seg for å bruke den globale tolkelåsen. Det andre alternativet var å legge til en lås til hvert objekt som ville ha resultert i fastlåsning og økt overhead fra anskaffe () og frigjør () samtaler.
Derfor er GIL en betydelig begrensning for flertrådede pythonprogrammer som kjører tunge CPU-bundne operasjoner (noe som gjør dem enkelttråder). Hvis du vil bruke flere CPU-kjerner i applikasjonen din, kan du bruke multibearbeidingsmodulen i stedet.
Sammendrag
- Python støtter to moduler for multitråding:
- __trådmodul : Den gir implementering på lavt nivå for tråding og er foreldet.
- threading module : Det gir en implementering på høyt nivå for multithreading og er gjeldende standard.
- For å lage en tråd ved hjelp av trådmodulen, må du gjøre følgende:
- Lag en klasse som utvider trådklassen .
- Overstyr konstruktøren (__init__).
- Overstyr run () -metoden.
- Lag et objekt av denne klassen.
- En tråd kan utføres ved å ringe til start () -metoden.
- Den delta () metode kan bli anvendt for å blokkere andre tråder til denne tråden (den på hvilken delta het) slo utførelse.
- En løpstilstand oppstår når flere tråder får tilgang til eller endrer en delt ressurs samtidig.
- Det kan unngås ved å synkronisere tråder.
- Python støtter 6 måter å synkronisere tråder på:
- Låser
- RLocks
- Semaforer
- Forhold
- Arrangementer og
- Barrierer
- Låser tillater bare en bestemt tråd som har anskaffet låsen, å komme inn i den kritiske delen.
- En lås har to primære metoder:
- anskaffe () : Den setter låsetilstanden til låst. Hvis det kalles på et låst objekt, blokkeres det til ressursen er gratis.
- release () : Den setter låsetilstanden til ulåst og returnerer. Hvis det kalles på et ulåst objekt, returnerer det falskt.
- Den globale tolkelåsen er en mekanisme der bare en CPython-tolkeprosess kan utføres om gangen.
- Den ble brukt for å lette referansetellingfunksjonaliteten til CPythons søppeloppsamler.
- For å lage Python-apper med tunge CPU-bundne operasjoner, bør du bruke multiprosesseringsmodulen.