Med programmeringsspråket för python kan du använda multiprocessing eller multithreading. I denna handledning lär du dig hur du skriver multitrådade applikationer i Python.
Vad är en tråd?
En tråd är en exektionsenhet vid samtidig programmering. Multithreading är en teknik som gör att en CPU kan utföra många uppgifter i en process samtidigt. Dessa trådar kan köras individuellt medan de delar sina processresurser.
Vad är en process?
En process är i grunden programmet som körs. När du startar ett program på din dator (som en webbläsare eller textredigerare) skapar operativsystemet en process.
Vad är Multithreading i Python?
Multithreading i Python- programmering är en välkänd teknik där flera trådar i en process delar sitt datautrymme med huvudtråden vilket gör informationsdelning och kommunikation inom trådarna enkelt och effektivt. Trådar är lättare än processer. Multitrådar kan köras individuellt medan de delar sina processresurser. Syftet med multithreading är att köra flera uppgifter och fungera celler samtidigt.
Vad är multiprocessing?
Multiprocessing låter dig köra flera oberoende processer samtidigt. Dessa processer delar inte sina resurser och kommunicerar via IPC.
Python Multithreading vs Multiprocessing
För att förstå processer och trådar, överväg detta scenario: En .exe-fil på din dator är ett program. När du öppnar det laddar operativsystemet det i minnet och CPU: n kör det. Programmet som nu körs kallas processen.
Varje process kommer att ha två grundläggande komponenter:
- Koden
- Uppgifterna
Nu kan en process innehålla en eller flera underdelar som kallas trådar. Detta beror på OS-arkitekturen. Du kan tänka på en tråd som en del av processen som kan köras separat av operativsystemet.
Med andra ord är det en ström av instruktioner som kan köras oberoende av operativsystemet. Trådar i en enda process delar data från den processen och är utformade för att arbeta tillsammans för att underlätta parallellitet.
I den här handledningen lär du dig,
- Vad är en tråd?
- Vad är en process?
- Vad är multithreading?
- Vad är multiprocessing?
- Python Multithreading vs Multiprocessing
- Varför använda Multithreading?
- Python MultiThreading
- Tråd- och trådmodulerna
- Trådmodulen
- Trådmodulen
- Blockeringar och rasförhållanden
- Synkroniserar trådar
- Vad är GIL?
- Varför behövdes GIL?
Varför använda Multithreading?
Multithreading låter dig dela upp en applikation i flera underuppgifter och köra dessa uppgifter samtidigt. Om du använder multitrådning på rätt sätt kan din applikationshastighet, prestanda och rendering förbättras.
Python MultiThreading
Python stöder konstruktioner för både multiprocessing och multithreading. I den här handledningen kommer du främst att fokusera på att implementera flertrådade applikationer med python. Det finns två huvudmoduler som kan användas för att hantera trådar i Python:
- Den gängmodulen och
- Den gängmodulen
Men i python finns det också något som kallas ett globalt tolklås (GIL). Det tillåter inte mycket prestandavinst och kan till och med minska prestandan för vissa flertrådade applikationer. Du lär dig allt om det i de kommande avsnitten i denna handledning.
Tråd- och trådmodulerna
De två modulerna som du kommer att lära dig om i denna handledning är trådmodulen och trådmodulen .
Trådmodulen har dock länge upphört. Från och med Python 3 har den betecknats som föråldrad och är endast tillgänglig som __thread för bakåtkompatibilitet.
Du bör använda den överordnade gängmodul för program som du tänker använda. Trådmodulen har endast täckts här för utbildningsändamål.
Trådmodulen
Syntaxen för att skapa en ny tråd med den här modulen är som följer:
thread.start_new_thread(function_name, arguments)
Okej, nu har du täckt grundläggande teorin för att börja koda. Så öppna din IDLE eller ett anteckningsblock och skriv in följande:
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))
Spara filen och tryck på F5 för att köra programmet. Om allt gjordes korrekt är det resultatet du ska se:
Du kommer att lära dig mer om tävlingsförhållanden och hur du hanterar dem i de kommande avsnitten
KODFÖRKLARING
- Dessa uttalanden importerar tids- och trådmodulen som används för att hantera körning och fördröjning av Python-trådarna.
- Här har du definierat en funktion som heter thread_test, som kommer att kallas av metoden start_new_thread . Funktionen körs en stundslinga för fyra iterationer och skriver ut namnet på den tråd som kallade den. När iterationen är klar skriver det ut ett meddelande som säger att tråden har slutförts.
- Detta är huvuddelen av ditt program. Här kallar du helt enkelt metoden start_new_thread med thread_test- funktionen som ett argument.
Detta skapar en ny tråd för den funktion du skickar som argument och börjar köra den. Observera att du kan ersätta detta (tråd _ test) med vilken annan funktion som du vill köra som en tråd.
Trådmodulen
Den här modulen är implementeringen av trådning i python på hög nivå och de facto-standarden för hantering av multitrådade applikationer. Det ger ett brett utbud av funktioner jämfört med trådmodulen.

Här är en lista över användbara funktioner som definierats i den här modulen:
Funktionsnamn | Beskrivning |
activeCount () | Returnerar antalet trådobjekt som fortfarande lever |
currentThread () | Returnerar det aktuella objektet i trådklassen. |
räkna upp() | Listar alla aktiva trådobjekt. |
isDaemon () | Returnerar sant om tråden är en demon. |
lever() | Returnerar sant om tråden fortfarande lever. |
Trådklassmetoder | |
Start() | Startar en tråds aktivitet. Det måste bara anropas en gång för varje tråd eftersom det kommer att kasta ett runtime-fel om det anropas flera gånger. |
springa() | Den här metoden anger en tråds aktivitet och kan åsidosättas av en klass som utökar trådklassen. |
Ansluta sig() | Det blockerar körningen av annan kod tills den tråd som metoden join () kallades till avslutas. |
Backstory: Trådklassen
Innan du börjar koda flertrådade program med hjälp av trådmodulen är det viktigt att du förstår om trådklassen. Trådklassen är den primära klassen som definierar mallen och funktionerna för en tråd i python.
Det vanligaste sättet att skapa en multitrådad pythonapplikation är att deklarera en klass som utökar trådklassen och åsidosätter den kör () -metoden.
Thread klassen, sammanfattningsvis, betecknar en kodsekvens som löper i en separat tråd av kontroll.
Så när du skriver en flertrådad app kommer du att göra följande:
- definiera en klass som utökar trådklassen
- Åsidosätta __init__ konstruktören
- Åsidosätt metoden run ()
När ett trådobjekt har skapats kan start () -metoden användas för att påbörja körningen av denna aktivitet och metoden join () kan användas för att blockera all annan kod tills den aktuella aktiviteten är klar.
Låt oss nu försöka använda trådmodulen för att implementera ditt tidigare exempel. Återigen, skjut upp din IDLE och skriv in följande:
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()
Detta kommer att vara resultatet när du kör ovanstående kod:
KODFÖRKLARING
- Denna del är densamma som vårt tidigare exempel. Här importerar du tids- och trådmodulen som används för att hantera körning och förseningar av Python-trådarna.
- I den här biten skapar du en klass som heter threadtester, som ärver eller utökar trådklassen för threading-modulen. Detta är ett av de vanligaste sätten att skapa trådar i python. Du bör dock bara åsidosätta konstruktören och metoden run () i din app. Som du kan se i ovanstående kodexempel har __init__- metoden (konstruktör) åsidosatts.
På samma sätt har du också åsidosatt metoden run () . Den innehåller koden som du vill köra inuti en tråd. I det här exemplet har du kallat funktionen thread_test ().
- Detta är thread_test () -metoden som tar värdet på i som ett argument, minskar det med 1 vid varje iteration och slingrar igenom resten av koden tills jag blir 0. I varje iteration skriver det ut namnet på den för närvarande körande tråden och sover i väntesekunder (vilket också tas som ett argument).
- thread1 = threadtester (1, "First Thread", 1)
Här skapar vi en tråd och skickar de tre parametrarna som vi förklarade i __init__. Den första parametern är trådens id, den andra parametern är trådens namn och den tredje parametern är räknaren som bestämmer hur många gånger while-slingan ska köras.
- thread2.start ()
Startmetoden används för att starta körningen av en tråd. Internt kallar start () -funktionen run () -metoden i din klass.
- thread3.join ()
Metoden join () blockerar körningen av annan kod och väntar tills den tråd som den kallades på slutar.
Som du redan vet har trådarna i samma process tillgång till minnet och data för den processen. Som ett resultat, om mer än en tråd försöker ändra eller få åtkomst till data samtidigt, kan fel krypa in.
I nästa avsnitt kommer du att se de olika typerna av komplikationer som kan dyka upp när trådar får åtkomst till data och kritisk sektion utan att söka efter befintliga åtkomsttransaktioner.
Blockeringar och rasförhållanden
Innan du lär dig mer om blockeringar och rasförhållanden kan det vara till hjälp att förstå några grundläggande definitioner relaterade till samtidig programmering:
- Kritisk sektion
Det är ett fragment av kod som får åtkomst till eller modifierar delade variabler och måste utföras som en atomtransaktion.
- Context Switch
Det är processen som en CPU följer för att lagra trådens tillstånd innan den byter från en uppgift till en annan så att den kan återupptas från samma punkt senare.
Blockeringar
Dödlås är det mest fruktade problemet som utvecklare står inför när de skriver samtidiga / flertrådade applikationer i python. Det bästa sättet att förstå dödlägen är att använda det klassiska datavetenskapliga exempelproblemet som kallas Dining Philosophers Problem.
Problemet för matfilosofer är följande:
Fem filosofer sitter på ett runt bord med fem spaghettiplattor (en pastatyp) och fem gafflar, som visas i diagrammet.

När som helst måste en filosof antingen äta eller tänka.
Dessutom måste en filosof ta de två gafflarna intill honom (dvs vänster och höger gafflar) innan han kan äta spagettin. Problemet med dödläge inträffar när alla fem filosofer plockar upp sina högra gafflar samtidigt.
Eftersom var och en av filosoferna har en gaffel kommer de alla att vänta på att de andra ska lägga ner sin gaffel. Som ett resultat kommer ingen av dem att kunna äta spaghetti.
På samma sätt inträffar ett dödläge i ett samtidigt system när olika trådar eller processer (filosofer) försöker förvärva de delade systemresurserna (gafflar) samtidigt. Som ett resultat får ingen av processerna en chans att utföra eftersom de väntar på en annan resurs som innehas av någon annan process.
Tävlingsförhållanden
Ett tävlingsförhållande är ett oönskat tillstånd i ett program som inträffar när ett system utför två eller flera operationer samtidigt. Tänk till exempel på detta enkelt för loop:
i=0; # a global variablefor x in range(100):print(i)i+=1;
Om du skapar n antal trådar som löper denna kod på en gång, kan du inte bestämma värdet på i (som delas av de trådar) när programmet avslutas utförandet. Detta beror på att trådarna i en riktig multitrådsmiljö kan överlappa varandra och värdet på i som hämtades och modifierades av en tråd kan förändras mellan när någon annan tråd kommer åt den.
Det här är de två huvudklasserna av problem som kan uppstå i en multitrådad eller distribuerad pythonapplikation. I nästa avsnitt lär du dig hur du löser problemet genom att synkronisera trådar.
Synkroniserar trådar
För att hantera tävlingsförhållanden, blockeringar och andra trådbaserade problem tillhandahåller trådningsmodulen Lock- objektet. Tanken är att när en tråd vill ha tillgång till en specifik resurs, förvärvar den ett lås för den resursen. När en tråd låser en viss resurs kan ingen annan tråd komma åt den förrän låset släpps. Som ett resultat kommer ändringarna av resursen attomiska och rasförhållandena kommer att avvärjas.
Ett lås är en primitiv synkroniseringsprimitiv implementerad av __thread- modulen. Vid varje given tidpunkt kan ett lås vara i ett av två tillstånd: låst eller olåst. Den stöder två metoder:
- tillägna sig()
När låsetillståndet är upplåst ändras tillståndet till låst och återgår genom att anropa förvärvsmetoden (). Om tillståndet är låst blockeras dock samtalet att förvärva () tills frigöringsmetoden () anropas av någon annan tråd.
- släpp()
Release () -metoden används för att ställa in tillståndet till upplåst, dvs för att frigöra ett lås. Det kan kallas av vilken tråd som helst, inte nödvändigtvis den som förvärvade låset.
Här är ett exempel på hur du använder lås i dina appar. Avfyra din IDLE och skriv följande:
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()
Nu, slå F5. Du bör se en utdata så här:
KODFÖRKLARING
- Här skapar du helt enkelt ett nytt lås genom att ringa threading.Lock () fabriksfunktionen. Internt returnerar Lock () en instans av den mest effektiva betonglåsklassen som underhålls av plattformen.
- I det första uttalandet får du låset genom att anropa förvärvet () -metoden. När låset har beviljats skriver du ut "lås förvärvat" till konsolen. När all kod som du vill att tråden ska köras har slutförts, släpper du låset genom att ringa till release () -metoden.
Teorin är bra, men hur vet du att låset verkligen fungerade? Om du tittar på utdata ser du att vart och ett av utskriftsuttalanden skriver ut exakt en rad i taget. Kom ihåg att i ett tidigare exempel var utdata från utskrift slumpmässiga eftersom flera trådar hade åtkomst till utskriftsmetoden () samtidigt. Här anropas utskriftsfunktionen först efter att låset förvärvats. Så, utgångarna visas en i taget och rad för rad.
Förutom lås stöder python också några andra mekanismer för att hantera trådsynkronisering enligt nedan:
- RLocks
- Semaforer
- Betingelser
- Händelser och
- Barriärer
Globalt tolklås (och hur man hanterar det)
Innan vi går in i detaljerna i pythons GIL, låt oss definiera några termer som är användbara för att förstå det kommande avsnittet:
- CPU-bunden kod: det här avser vilken kod som helst som kommer att köras direkt av CPU: n.
- I / O-bunden kod: detta kan vara vilken kod som helst som har åtkomst till filsystemet genom operativsystemet
- CPython: det är referens genomförandet av Python och kan beskrivas som tolken skriven i C och Python (programmeringsspråk).
Vad är GIL i Python?
Global Interpreter Lock (GIL) i python är ett processlås eller en mutex som används när man hanterar processerna. Det ser till att en tråd kan komma åt en viss resurs åt gången och det förhindrar också användning av objekt och bytekoder på en gång. Detta gynnar de enkla trådarna i en prestationsökning. GIL i python är väldigt enkelt och enkelt att implementera.
Ett lås kan användas för att se till att endast en tråd har åtkomst till en viss resurs vid en given tidpunkt.
En av funktionerna i Python är att den använder ett globalt lås på varje tolkprocess, vilket innebär att varje process behandlar själva pythontolken som en resurs.
Antag till exempel att du har skrivit ett pythonprogram som använder två trådar för att utföra både CPU- och 'I / O' -operationer. När du kör det här programmet händer det här:
- Pythontolken skapar en ny process och skapar trådarna
- När tråd-1 börjar köra kommer den först att skaffa GIL och låsa den.
- Om tråd-2 vill köra nu måste den vänta tills GIL släpps även om en annan processor är gratis.
- Antag nu att tråd-1 väntar på en I / O-operation. För närvarande släpper den GIL och thread-2 kommer att förvärva den.
- Efter avslutad I / O-ops, om tråd-1 vill utföra nu, måste den åter vänta på att GIL släpps av tråd-2.
På grund av detta kan bara en tråd komma åt tolk när som helst, vilket innebär att det bara kommer att finnas en tråd som kör pythonkod vid en given tidpunkt.
Det här är okej i en processor med en enda kärna eftersom den skulle använda tidsskivning (se det första avsnittet i denna handledning) för att hantera trådarna. I fallet med processorer med flera kärnor kommer en CPU-bunden funktion som körs på flera trådar att ha en betydande inverkan på programmets effektivitet, eftersom den faktiskt inte kommer att använda alla tillgängliga kärnor samtidigt.
Varför behövdes GIL?
CPython-skräpsamlaren använder en effektiv minneshanteringsteknik som kallas referensräkning. Så här fungerar det: Varje objekt i python har ett referensantal, som ökas när det tilldelas ett nytt variabelnamn eller läggs till i en behållare (som tuplar, listor etc.). På samma sätt minskar referensantalet när referensen går utanför räckvidden eller när del-uttalandet anropas. När referensantalet för ett objekt når 0, samlas det skräp och det tilldelade minnet frigörs.
Men problemet är att referensräkningsvariabeln är utsatt för rasförhållanden som alla andra globala variabler. För att lösa detta problem bestämde utvecklarna av python att använda det globala tolklåset. Det andra alternativet var att lägga till ett lås till varje objekt som skulle ha resulterat i blockeringar och ökade omkostnader från anskaffa () och släpp () samtal.
Därför är GIL en betydande begränsning för flertrådiga pythonprogram som kör tunga CPU-bundna operationer (vilket gör dem effektivt med en tråd). Om du vill använda flera CPU-kärnor i din applikation, använd istället multiprocessing- modulen.
Sammanfattning
- Python stöder två moduler för multithreading:
- __thread- modul: Den ger en låg nivå implementering för trådning och är föråldrad.
- trådmodul : Det ger en implementering på hög nivå för multitrådning och är den nuvarande standarden.
- För att skapa en tråd med trådmodulen måste du göra följande:
- Skapa en klass som utökar trådklassen .
- Åsidosätt dess konstruktör (__init__).
- Åsidosätt dess run () -metod.
- Skapa ett objekt av den här klassen.
- En tråd kan köras genom att anropa start () -metoden.
- Den join () metoden kan användas för att blockera andra trådar tills denna tråd (den på vilken ansluta kallades) avslutar exekveringen.
- Ett tävlingsvillkor uppstår när flera trådar får åtkomst till eller ändrar en delad resurs samtidigt.
- Det kan undvikas genom att synkronisera trådar.
- Python stöder 6 sätt att synkronisera trådar:
- Lås
- RLocks
- Semaforer
- Betingelser
- Händelser och
- Barriärer
- Lås tillåter endast en viss tråd som har förvärvat låset att komma in i det kritiska avsnittet.
- Ett lås har två primära metoder:
- förvärva () : Det sätter låsetillståndet till låst. Om det anropas på ett låst objekt blockeras det tills resursen är gratis.
- release () : Det ställer in låsetillståndet till upplåst och återgår. Om det anropas på ett olåst objekt returnerar det falskt.
- Det globala tolklåset är en mekanism genom vilken endast en CPython-tolkprocess kan köras åt gången.
- Den användes för att underlätta referensräkningsfunktionaliteten hos CPythons sopuppsamlare.
- För att göra Python-appar med tunga CPU-bundna operationer bör du använda multiprocessing-modulen.