Python Threading: An Introduction – adminvista.com

By rik

I den här guiden kommer vi att undersöka hur du kan utnyttja Pythons inbyggda modul för trådar, för att utforska möjligheterna med trådhantering i Python.

Vi börjar med en genomgång av grunderna kring processer och trådar, för att sedan utforska hur trådning fungerar i Python. Du kommer att få en djupare förståelse för begreppen samtidighet och parallellism. Därefter går vi igenom hur du initierar och kör en eller flera trådar med hjälp av den medföljande trådmodulen.

Låt oss dyka in!

Processer kontra Trådar: Vad är skillnaderna?

Vad är en process?

En process kan ses som en instans av ett program som behöver köras.

Det kan vara allt från ett enkelt Python-skript, till en webbläsare som Chrome eller en videokonferensapplikation. Om du öppnar Aktivitetshanteraren på din dator och navigerar till Prestanda -> CPU, kommer du att kunna se de processer och trådar som för närvarande körs på dina CPU-kärnor.

Förstå Processer och Trådar

Internt har varje process ett dedikerat minnesutrymme som lagrar den kod och data som tillhör just den processen.

En process består av en eller flera trådar. En tråd är den minsta enheten av instruktioner som ett operativsystem kan köra, och den representerar flödet av exekvering.

Varje tråd har sin egen stack och register, men delar processens dedikerade minne. Alla trådar som hör till en viss process kan därmed komma åt samma data och minne.

En CPU med N kärnor kan köra N processer parallellt samtidigt. Dock kan två trådar som tillhör samma process inte köras parallellt, utan istället simultant. Vi kommer att undersöka skillnaden mellan samtidighet och parallellism mer i detalj i nästa avsnitt.

Baserat på vad vi har gått igenom hittills, låt oss sammanfatta skillnaderna mellan processer och trådar.

Funktion Process Tråd
Minne Dedikerat minne Delat minne
Exekveringsläge Parallellt, samtidigt Samtidigt; men inte parallell exekvering som hanteras av operativsystemet

Multithreading i Python

I Python ser Global Interpreter Lock (GIL) till att endast en tråd kan få tillgång till låset och köra vid en viss tidpunkt. Alla trådar måste ha detta lås för att kunna köras. Detta säkerställer att bara en tråd kan köras åt gången, vilket förhindrar samtidig flertrådning.

Tänk dig till exempel två trådar, t1 och t2, som tillhör samma process. Eftersom trådar delar samma data, kan t2 ändra ett värde k samtidigt som t1 läser det. Detta kan leda till låsningar och oönskade resultat. GIL ser dock till att endast en av trådarna kan erhålla låset och köra i taget, vilket i sin tur säkerställer trådsäkerhet.

Så hur åstadkommer vi egentligen flertrådning i Python? För att förstå detta behöver vi diskutera begreppen samtidighet och parallellism.

Samtidighet kontra Parallellism: En översikt

Föreställ dig en CPU med mer än en kärna. I illustrationen nedan har CPU:n fyra kärnor, vilket innebär att vi kan ha fyra olika operationer som körs parallellt samtidigt.

Om vi har fyra processer kan var och en av dem köras oberoende och samtidigt på var sin kärna. Låt oss anta att varje process i sin tur har två trådar.

För att förstå hur trådning fungerar, låt oss byta från en flerkärnig till en enkelkärnig processorarkitektur. Som tidigare nämnts kan endast en tråd vara aktiv under en given exekveringstid, men processorkärnan kan växla mellan olika trådar.

Till exempel väntar ofta I/O-bundna trådar på I/O-operationer: inläsning från användare, databasförfrågningar, och filoperationer. Under dessa väntetider kan tråden släppa låset så att en annan tråd kan köras. Väntetiden kan också vara en enkel operation som att ”sova” i några sekunder.

Sammanfattningsvis: under väntetider släpper tråden låset, vilket gör att processorkärnan kan växla till en annan tråd. Den tidigare tråden återupptar sin körning när väntetiden är över. Denna process, där processorkärnan växlar mellan trådarna samtidigt, gör multithreading möjligt. ✅

Om du istället vill implementera parallellitet på processnivå i din applikation kan du överväga att använda multiprocessing.

Python Threading Module: Första stegen

Python har en inbyggd trådmodul som du enkelt kan importera i dina Python-skript.

import threading

För att skapa ett trådobjekt i Python använder du trådkonstruktorn: threading.Thread(...). Detta är den generella syntaxen som är tillräcklig för de flesta trådimplementationer:

threading.Thread(target=...,args=...)

Här,

  • target är det nyckelordsargument som anger en Python callable (en funktion eller metod)
  • args är en tuple som innehåller argument som ska skickas in till target.

Du behöver Python 3.x för att kunna köra kodexemplen i denna guide. Ladda ner koden och följ med.

Hur man Definierar och Kör Trådar i Python

Låt oss definiera en tråd som kör en specifik funktion.

Målfunktionen är `some_func`.

import threading
import time

def some_func():
    print("Running some_func...")
    time.sleep(2)
    print("Finished running some_func.")

thread1 = threading.Thread(target=some_func)
thread1.start()
print(threading.active_count())

Låt oss analysera vad kodavsnittet gör:

  • Den importerar tråd- och tidsmodulerna.
  • Funktionen some_func innehåller beskrivande print()-satser och en sleep-operation som pausar tråden i två sekunder: time.sleep(n) gör att funktionen vilar i n sekunder.
  • Därefter definierar vi en tråd thread_1 med some_func som mål. threading.Thread(target=...) skapar ett trådobjekt.
  • Obs: Använd namnet på funktionen, inte ett funktionsanrop. Använd some_func istället för some_func().
  • Att skapa ett trådobjekt startar inte själva tråden. För att göra det, anropar du metoden start() på trådobjektet.
  • För att få antalet aktiva trådar använder vi funktionen active_count().

Python-skriptet körs i huvudtråden, och vi skapar en till tråd (thread1) för att köra funktionen some_func, vilket resulterar i att antalet aktiva trådar är två, vilket framgår av utskriften:

# Utskrift
Running some_func...
2
Finished running some_func.

Om vi tittar närmare på utskriften ser vi att när tråd1 startas körs den första utskriftssatsen. Men under viloläget växlar processorn till huvudtråden och skriver ut antalet aktiva trådar – utan att vänta på att tråd1 ska slutföras.

Vänta på att Trådar Ska Avsluta sin Exekvering

Om du vill att thread1 ska slutföra sin exekvering, kan du anropa metoden join() på den efter att tråden har startats. Då väntar du på att thread1 ska slutföras innan huvudtråden fortsätter.

import threading
import time

def some_func():
    print("Running some_func...")
    time.sleep(2)
    print("Finished running some_func.")

thread1 = threading.Thread(target=some_func)
thread1.start()
thread1.join()
print(threading.active_count())

Nu har thread1 körts klart innan vi skriver ut antalet aktiva trådar. Nu körs endast huvudtråden, vilket betyder att antalet aktiva trådar är ett. ✅

# Utskrift
Running some_func...
Finished running some_func.
1

Hur Man Kör Flera Trådar i Python

Låt oss nu skapa två trådar för att köra två olika funktioner.

Här är count_down en funktion som tar in ett tal som argument och räknar ner från det talet till noll.

def count_down(n):
    for i in range(n,-1,-1):
        print(i)

Vi definierar även count_up, en annan Python-funktion som räknar från noll upp till ett givet tal.

def count_up(n):
    for i in range(n+1):
        print(i)

📑 När du använder funktionen range() med syntaxen range(start, stop, steg), exkluderas slutpunkten som standard.

  • För att räkna ner från ett specifikt tal till noll kan du använda ett negativt stegvärde på -1 och sätta stoppvärdet till -1 så att noll inkluderas.
  • På samma sätt, för att räkna upp till n, behöver du sätta stoppvärdet till n+1. Eftersom standardvärdena för start och steg är 0 respektive 1 kan du använda range(n+1) för att få sekvensen från 0 till och med n.

Därefter definierar vi två trådar, thread1 och thread2 för att köra funktionerna count_down respektive count_up. Vi lägger även till utskrifter och vilolägen för båda funktionerna.

När du skapar trådobjekten, notera att argumenten som skickas till målfunktionen ska anges som en tupel i parametern args. Eftersom både count_down och count_up tar ett argument behöver du lägga till ett kommatecken efter värdet. Detta säkerställer att argumentet fortfarande tolkas som en tupel, eftersom efterföljande element antas vara None.

import threading
import time

def count_down(n):
    for i in range(n,-1,-1):
        print("Running thread1....")
        print(i)
        time.sleep(1)


def count_up(n):
    for i in range(n+1):
        print("Running thread2...")
        print(i)
        time.sleep(1)

thread1 = threading.Thread(target=count_down,args=(10,))
thread2 = threading.Thread(target=count_up,args=(5,))
thread1.start()
thread2.start()

I utskriften:

  • Funktionen count_up körs i thread2 och räknar upp till 5 från 0.
  • Funktionen count_down körs i thread1 och räknar ner från 10 till 0.
# Utskrift
Running thread1....
10
Running thread2...
0
Running thread1....
9
Running thread2...
1
Running thread1....
8
Running thread2...
2
Running thread1....
7
Running thread2...
3
Running thread1....
6
Running thread2...
4
Running thread1....
5
Running thread2...
5
Running thread1....
4
Running thread1....
3
Running thread1....
2
Running thread1....
1
Running thread1....
0

Du ser att thread1 och thread2 körs omväxlande, eftersom båda innehåller en väntoperation (sleep). När count_up har räknat upp till 5, är tråd 2 inte längre aktiv. Därmed ser vi endast utskrifter från tråd 1.

Sammanfattning

I den här handledningen har du lärt dig hur du använder Pythons inbyggda trådmodul för att implementera multithreading. Här är en sammanfattning av de viktigaste punkterna:

  • Trådkonstruktorn kan användas för att skapa ett trådobjekt. Genom att använda threading.Thread(target=<callable>, args=(<tuple of args>)) skapas en tråd som kör den givna funktionen eller metoden med argumenten som anges i args.
  • Python-programmet körs i en huvudtråd. Trådobjekten du skapar är alltså ytterligare trådar. Funktionen active_count() returnerar antalet aktiva trådar vid ett givet tillfälle.
  • Du kan starta en tråd med metoden start() på trådobjektet och vänta tills den är klar med sin exekvering genom att använda metoden join().

Du kan experimentera vidare genom att justera väntetiderna, testa andra I/O-operationer och mer. Kom ihåg att implementera multithreading i dina kommande Python-projekt. Lycka till med kodningen!🎉