Industriële fabricage
Industrieel internet der dingen | Industriële materialen | Onderhoud en reparatie van apparatuur | Industriële programmering |
home  MfgRobots >> Industriële fabricage >  >> Industrial programming >> Python

Multithreading in Python met voorbeeld:leer GIL in Python

Met de programmeertaal Python kunt u multiprocessing of multithreading gebruiken. In deze tutorial leer je hoe je multithreaded applicaties schrijft in Python.

Wat is een discussielijn?

Een thread is een uitvoeringseenheid bij gelijktijdige programmering. Multithreading is een techniek waarmee een CPU veel taken van één proces tegelijkertijd kan uitvoeren. Deze threads kunnen afzonderlijk worden uitgevoerd terwijl ze hun procesbronnen delen.

Wat is een proces?

Een proces is in feite het programma in uitvoering. Wanneer u een toepassing op uw computer start (zoals een browser of teksteditor), maakt het besturingssysteem een ​​proces aan.

Wat is multithreading in Python?

Multithreading in Python programmeren is een bekende techniek waarbij meerdere threads in een proces hun dataruimte delen met de hoofdthread, wat het delen van informatie en communicatie binnen threads gemakkelijk en efficiënt maakt. Draden zijn lichter dan processen. Multi-threads kunnen afzonderlijk worden uitgevoerd terwijl ze hun procesbronnen delen. Het doel van multithreading is om meerdere taken en functiecellen tegelijkertijd uit te voeren.

In deze tutorial leer je,

  • Wat is een draad?
  • Wat is een proces?
  • Wat is multithreading?
  • Wat is multiprocessing?
  • Python Multithreading versus Multiprocessing
  • Waarom multithreading gebruiken?
  • Python MultiThreading
  • De draad- en draadmodules
  • De draadmodule
  • De inrijgmodule
  • Deadlocks en racevoorwaarden
  • Draden synchroniseren
  • Wat is GIL?
  • Waarom was GIL nodig?

Wat is multiprocessing?

Met multiprocessing kunt u meerdere niet-gerelateerde processen tegelijkertijd uitvoeren. Deze processen delen hun bronnen niet en communiceren niet via IPC.

Python Multithreading versus Multiprocessing

Overweeg dit scenario om processen en threads te begrijpen:Een .exe-bestand op uw computer is een programma. Wanneer u het opent, laadt het besturingssysteem het in het geheugen en voert de CPU het uit. De instantie van het programma dat nu draait, wordt het proces genoemd.

Elk proces heeft 2 fundamentele componenten:

  • De code
  • De gegevens

Nu kan een proces een of meer subonderdelen bevatten die threads worden genoemd. Dit hangt af van de OS-architectuur. Je kunt een thread beschouwen als een onderdeel van het proces dat afzonderlijk door het besturingssysteem kan worden uitgevoerd.

Met andere woorden, het is een stroom van instructies die onafhankelijk door het besturingssysteem kan worden uitgevoerd. Threads binnen een enkel proces delen de gegevens van dat proces en zijn ontworpen om samen te werken om parallellisme te vergemakkelijken.

Waarom multithreading gebruiken?

Met multithreading kunt u een toepassing opsplitsen in meerdere subtaken en deze taken tegelijkertijd uitvoeren. Als u multithreading op de juiste manier gebruikt, kunnen uw applicatiesnelheid, prestaties en weergave allemaal worden verbeterd.

Python MultiThreading

Python ondersteunt constructies voor zowel multiprocessing als multithreading. In deze zelfstudie richt je je voornamelijk op het implementeren van multithreaded toepassingen met python. Er zijn twee hoofdmodules die kunnen worden gebruikt om threads in Python af te handelen:

  1. De thread module, en
  2. De threading module

In python is er echter ook iets dat een global interpreter lock (GIL) wordt genoemd. Het zorgt niet voor veel prestatiewinst en kan zelfs verminderen de prestaties van sommige toepassingen met meerdere threads. Je zult er alles over leren in de komende secties van deze tutorial.

De modules Draad en Draadsnijden

De twee modules waarover u in deze zelfstudie leert, zijn de thread-module en de threading-module .

De threadmodule is echter al lang verouderd. Vanaf Python 3 is het als verouderd aangemerkt en is het alleen toegankelijk als __thread voor achterwaartse compatibiliteit.

U moet de threading op een hoger niveau gebruiken module voor toepassingen die u wilt implementeren. De thread-module is hier alleen behandeld voor educatieve doeleinden.

De draadmodule

De syntaxis om een ​​nieuwe thread te maken met behulp van deze module is als volgt:

thread.start_new_thread(function_name, arguments)

Oké, nu heb je de basistheorie behandeld om te beginnen met coderen. Open dus uw IDLE of een notitieblok en typ het volgende in:

import time
import _thread

def thread_test(name, wait):
   i = 0
   while i <= 3:
      time.sleep(wait)
      print("Running %s\n" %name)
      i = i + 1

   print("%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))


Sla het bestand op en druk op F5 om het programma uit te voeren. Als alles correct is gedaan, is dit de uitvoer die u zou moeten zien:

In de komende secties leer je meer over raceomstandigheden en hoe je ermee om moet gaan

CODE UITLEG

  1. Deze instructies importeren de tijd- en threadmodule die worden gebruikt om de uitvoering en vertraging van de Python-threads af te handelen.
  2. Hier heb je een functie gedefinieerd met de naam thread_test, die wordt aangeroepen door de start_new_thread methode. De functie voert een while-lus uit voor vier iteraties en drukt de naam af van de thread die deze heeft aangeroepen. Zodra de iteratie is voltooid, wordt een bericht afgedrukt dat de uitvoering van de thread is voltooid.
  3. Dit is het hoofdgedeelte van je programma. Hier roep je gewoon de start_new_thread . aan methode met de thread_test functie als een argument. Dit zal een nieuwe thread maken voor de functie die u als argument doorgeeft en deze beginnen uit te voeren. Merk op dat je dit kunt vervangen (thread_ test) met een andere functie die u als thread wilt gebruiken.

De inrijgmodule

Deze module is de implementatie op hoog niveau van threading in python en de de facto standaard voor het beheren van multithreaded-applicaties. Het biedt een breed scala aan functies in vergelijking met de threadmodule.

Hier is een lijst van enkele nuttige functies die in deze module zijn gedefinieerd:

Functienaam Beschrijving
activeCount() Retourneert het aantal Thread voorwerpen die nog leven
currentThread() Retourneert het huidige object van de Thread-klasse.
enumerate() Laat alle actieve Thread-objecten zien.
isDaemon() Retourneert true als de thread een daemon is.
isAlive() Retourneert true als de thread nog leeft.
Thread Class-methoden
start() Start de activiteit van een thread. Het moet slechts één keer worden aangeroepen voor elke thread, omdat het een runtime-fout zal geven als het meerdere keren wordt aangeroepen.
run() Deze methode geeft de activiteit van een thread aan en kan worden overschreven door een klasse die de klasse Thread uitbreidt.
join() Het blokkeert de uitvoering van andere code totdat de thread waarop de methode join() werd aangeroepen, wordt beëindigd.

Achterverhaal:The Thread Class

Voordat u begint met het coderen van multithreaded-programma's met behulp van de threading-module, is het cruciaal om de Thread-klasse te begrijpen. De thread-klasse is de primaire klasse die de sjabloon en de bewerkingen van een thread in python definieert.

De meest gebruikelijke manier om een ​​python-toepassing met meerdere threads te maken, is door een klasse te declareren die de klasse Thread uitbreidt en de methode run() overschrijft.

De klasse Thread, samengevat, betekent een codereeks die in een aparte thread wordt uitgevoerd van controle.

Dus als je een app met meerdere threads schrijft, doe je het volgende:

  1. definieer een klasse die de klasse Thread uitbreidt
  2. Overschrijf de __init__ constructeur
  3. Overschrijf de run() methode

Zodra een thread-object is gemaakt, wordt de start() methode kan worden gebruikt om de uitvoering van deze activiteit te starten en de join() methode kan worden gebruikt om alle andere code te blokkeren totdat de huidige activiteit is voltooid.

Laten we nu proberen de threading-module te gebruiken om uw vorige voorbeeld te implementeren. Start nogmaals uw IDLE en typ het volgende:

import time
import threading

class threadtester (threading.Thread):
    def __init__(self, id, name, i):
       threading.Thread.__init__(self)
       self.id = id
       self.name = name
       self.i = i
       
    def 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 - 1

if __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()

Dit zal de output zijn wanneer je de bovenstaande code uitvoert:

CODE UITLEG

  1. Dit deel is hetzelfde als ons vorige voorbeeld. Hier importeert u de tijd- en threadmodule die worden gebruikt om de uitvoering en vertragingen van de Python-threads af te handelen.
  2. In dit deel maak je een klasse met de naam threadtester, die de Thread erft of uitbreidt klasse van de draadsnijmodule. Dit is een van de meest gebruikelijke manieren om threads in python te maken. U moet echter alleen de constructor en de run() . overschrijven methode in uw app. Zoals je kunt zien in het bovenstaande codevoorbeeld, is de __init__ methode (constructor) is overschreven. Op dezelfde manier heb je ook de run() . overschreven methode. Het bevat de code die u in een thread wilt uitvoeren. In dit voorbeeld heb je de functie thread_test() aangeroepen.
  3. Dit is de methode thread_test() die de waarde van i . aanneemt als een argument, verlaagt het met 1 bij elke iteratie en loopt door de rest van de code totdat i 0 wordt. In elke iteratie drukt het de naam af van de momenteel uitgevoerde thread en slaapt het voor seconden wachten (wat ook als een argument wordt beschouwd ).
  4. thread1 =threadtester(1, "First Thread", 1) Hier maken we een thread en geven we de drie parameters door die we in __init__ hebben gedeclareerd. De eerste parameter is de id van de thread, de tweede parameter is de naam van de thread en de derde parameter is de teller, die bepaalt hoe vaak de while-lus moet worden uitgevoerd.
  5. thread2.start()D e startmethode wordt gebruikt om de uitvoering van een thread te starten. Intern roept de start() functie de run() methode van je klasse aan.
  6. thread3.join() De methode join() blokkeert de uitvoering van andere code en wacht tot de thread waarop deze werd aangeroepen, is voltooid.

Zoals u al weet, hebben de threads die zich in hetzelfde proces bevinden, toegang tot het geheugen en de gegevens van dat proces. Als gevolg hiervan, als meer dan één thread tegelijkertijd probeert de gegevens te wijzigen of toegang te krijgen, kunnen er fouten binnensluipen.

In het volgende gedeelte ziet u de verschillende soorten complicaties die kunnen optreden wanneer threads toegang krijgen tot gegevens en kritieke secties zonder te controleren op bestaande toegangstransacties.

Deadlocks en racevoorwaarden

Voordat u leert over deadlocks en race-omstandigheden, is het handig om enkele basisdefinities met betrekking tot gelijktijdig programmeren te begrijpen:

  • Kritische sectieHet is een codefragment dat toegang heeft tot gedeelde variabelen of deze wijzigt en moet worden uitgevoerd als een atomaire transactie.
  • Contextomschakeling Het is het proces dat een CPU volgt om de status van een thread op te slaan voordat hij van de ene taak naar de andere gaat, zodat deze later vanaf hetzelfde punt kan worden hervat.

Pauzes

Deadlocks zijn het meest gevreesde probleem waarmee ontwikkelaars worden geconfronteerd bij het schrijven van gelijktijdige/multithreaded applicaties in Python. De beste manier om impasses te begrijpen is door het klassieke computerwetenschappelijke voorbeeldprobleem te gebruiken dat bekend staat als het Dining Philosophers Problem.

De probleemstelling voor eetfilosofen is als volgt:

Vijf filosofen zitten aan een ronde tafel met vijf borden spaghetti (een soort pasta) en vijf vorken, zoals in het schema te zien is.

Op elk moment moet een filosoof eten of denken.

Bovendien moet een filosoof de twee vorken naast hem nemen (d.w.z. de linker- en rechtervork) voordat hij de spaghetti kan eten. Het probleem van een impasse doet zich voor wanneer alle vijf filosofen tegelijkertijd hun rechtervork oppakken.

Aangezien elk van de filosofen één vork heeft, zullen ze allemaal wachten tot de anderen hun vork neerleggen. Als gevolg hiervan zal geen van hen spaghetti kunnen eten.

Evenzo treedt in een gelijktijdig systeem een ​​impasse op wanneer verschillende threads of processen (filosofen) tegelijkertijd proberen de gedeelde systeembronnen (forks) te verwerven. Als gevolg hiervan krijgt geen van de processen de kans om uit te voeren omdat ze wachten op een andere bron die door een ander proces wordt vastgehouden.

Racevoorwaarden

Een raceconditie is een ongewenste toestand van een programma die optreedt wanneer een systeem twee of meer bewerkingen tegelijk uitvoert. Beschouw bijvoorbeeld deze eenvoudige for-lus:

i=0; # a global variable
for x in range(100):
    print(i)
    i+=1;

Als u n . maakt aantal threads dat deze code tegelijk uitvoert, kunt u de waarde van i (die wordt gedeeld door de threads) niet bepalen wanneer het programma klaar is met uitvoeren. Dit komt omdat in een echte multithreading-omgeving de threads elkaar kunnen overlappen, en de waarde van i die is opgehaald en gewijzigd door een thread kan tussentijds veranderen wanneer een andere thread er toegang toe heeft.

Dit zijn de twee belangrijkste soorten problemen die kunnen optreden in een multithreaded of gedistribueerde python-toepassing. In het volgende gedeelte leert u hoe u dit probleem kunt oplossen door threads te synchroniseren.

Draden synchroniseren

Om race-omstandigheden, deadlocks en andere op threads gebaseerde problemen aan te pakken, biedt de threading-module de Lock object. Het idee is dat wanneer een thread toegang wil tot een specifieke bron, deze een slot voor die bron krijgt. Zodra een thread een bepaalde bron vergrendelt, heeft geen enkele andere thread er toegang toe totdat de vergrendeling wordt vrijgegeven. Als gevolg hiervan zullen de wijzigingen aan de bron atomair zijn en worden race-omstandigheden afgewend.

Een vergrendeling is een primitief voor synchronisatie op laag niveau, geïmplementeerd door de __thread module. Een slot kan zich op elk moment in een van de 2 toestanden bevinden:vergrendeld of ontgrendeld. Het ondersteunt twee methoden:

  1. verkrijgen() Wanneer de lock-status is ontgrendeld, zal het aanroepen van de methode acquire() de status wijzigen in vergrendeld en terugkeren. Als de status echter is vergrendeld, wordt de aanroep van acquire() geblokkeerd totdat de methode release() wordt aangeroepen door een andere thread.
  2. release() De methode release() wordt gebruikt om de status in te stellen op ontgrendeld, d.w.z. om een ​​vergrendeling op te heffen. Het kan door elke thread worden aangeroepen, niet noodzakelijk degene die het slot heeft verkregen.

Hier is een voorbeeld van het gebruik van vergrendelingen in uw apps. Start uw IDLE op en typ het volgende:

import threading
lock = 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()

Druk nu op F5. Je zou een uitvoer als deze moeten zien:

CODE UITLEG

  1. Hier maakt u eenvoudig een nieuwe vergrendeling door de threading.Lock() aan te roepen fabrieksfunctie. Intern retourneert Lock() een instantie van de meest effectieve concrete Lock-klasse die door het platform wordt onderhouden.
  2. In de eerste instructie verkrijgt u de vergrendeling door de methode acquire() aan te roepen. Wanneer het slot is verleend, drukt u "slot verworven" . af naar de console. Zodra alle code die u de thread wilt laten uitvoeren, is uitgevoerd, geeft u de vergrendeling op door de methode release() aan te roepen.

De theorie is prima, maar hoe weet je dat het slot echt werkte? Als u naar de uitvoer kijkt, ziet u dat elk van de afdrukinstructies precies één regel tegelijk afdrukt. Bedenk dat in een eerder voorbeeld de uitvoer van print lukraak was omdat meerdere threads tegelijkertijd toegang hadden tot de methode print(). Hier wordt de afdrukfunctie pas aangeroepen nadat de vergrendeling is verkregen. De uitgangen worden dus één voor één en regel voor regel weergegeven.

Afgezien van vergrendelingen, ondersteunt python ook enkele andere mechanismen om threadsynchronisatie af te handelen, zoals hieronder vermeld:

  1. Rvergrendelingen
  2. Semaforen
  3. Voorwaarden
  4. Evenementen, en
  5. Barrières

Global Interpreter Lock (en hoe ermee om te gaan)

Voordat we ingaan op de details van python's GIL, laten we eerst een paar termen definiëren die nuttig zullen zijn bij het begrijpen van de komende sectie:

  1. CPU-gebonden code:dit verwijst naar elk stukje code dat direct door de CPU wordt uitgevoerd.
  2. I/O-gebonden code:dit kan elke code zijn die toegang heeft tot het bestandssysteem via het besturingssysteem
  3. CPython:het is de referentie implementatie van Python en kan worden omschreven als de tolk geschreven in C en Python (programmeertaal).

Wat is GIL in Python?

Global Interpreter Lock (GIL) in python is een procesvergrendeling of een mutex die wordt gebruikt bij het afhandelen van de processen. Het zorgt ervoor dat één thread tegelijkertijd toegang heeft tot een bepaalde bron en het voorkomt ook dat objecten en bytecodes tegelijk worden gebruikt. Dit komt de single-threaded programma's ten goede in een prestatieverhoging. GIL in python is heel eenvoudig en gemakkelijk te implementeren.

Een slot kan worden gebruikt om ervoor te zorgen dat slechts één thread tegelijkertijd toegang heeft tot een bepaalde bron.

Een van de kenmerken van Python is dat het een globaal slot op elk interpreterproces gebruikt, wat betekent dat elk proces de Python-interpreter zelf als een hulpbron behandelt.

Stel dat u bijvoorbeeld een python-programma hebt geschreven dat twee threads gebruikt om zowel CPU- als 'I/O'-bewerkingen uit te voeren. Als je dit programma uitvoert, gebeurt dit:

  1. De python-interpreter creëert een nieuw proces en spawnt de threads
  2. Als thread-1 begint te lopen, zal het eerst de GIL ophalen en vergrendelen.
  3. Als thread-2 nu wil worden uitgevoerd, moet het wachten tot de GIL wordt vrijgegeven, zelfs als een andere processor vrij is.
  4. Veronderstel nu dat thread-1 wacht op een I/O-bewerking. Op dit moment zal het de GIL vrijgeven, en thread-2 zal het verwerven.
  5. Na het voltooien van de I/O-operaties, als thread-1 nu wil worden uitgevoerd, zal het opnieuw moeten wachten tot de GIL wordt vrijgegeven door thread-2.

Hierdoor heeft slechts één thread tegelijkertijd toegang tot de interpreter, wat betekent dat er slechts één thread op een bepaald moment python-code uitvoert.

Dit is in orde in een single-coreprocessor omdat het time-slicing zou gebruiken (zie het eerste deel van deze tutorial) om de threads af te handelen. In het geval van multi-coreprocessors zal een CPU-gebonden functie die wordt uitgevoerd op meerdere threads echter een aanzienlijke impact hebben op de efficiëntie van het programma, aangezien het niet alle beschikbare cores tegelijkertijd zal gebruiken.

Waarom was GIL nodig?

De CPython-afvalverzamelaar gebruikt een efficiënte geheugenbeheertechniek die bekend staat als referentietelling. Zo werkt het:elk object in python heeft een referentietelling, die wordt verhoogd wanneer het wordt toegewezen aan een nieuwe variabelenaam of wordt toegevoegd aan een container (zoals tupels, lijsten, enz.). Evenzo wordt het aantal referenties verlaagd wanneer de referentie buiten het bereik valt of wanneer de del-instructie wordt aangeroepen. Wanneer de referentietelling van een object 0 bereikt, wordt het afval verzameld en wordt het toegewezen geheugen vrijgemaakt.

Maar het probleem is dat de referentietellingsvariabele net als elke andere globale variabele gevoelig is voor race-omstandigheden. Om dit probleem op te lossen, hebben de ontwikkelaars van python besloten om de global interpreter lock te gebruiken. De andere optie was om een ​​vergrendeling toe te voegen aan elk object, wat zou hebben geleid tot impasses en meer overhead van de aanroepen van acquire() en release().

Daarom is GIL een belangrijke beperking voor python-programma's met meerdere threads die zware CPU-gebonden bewerkingen uitvoeren (waardoor ze effectief single-threaded worden). Als je gebruik wilt maken van meerdere CPU-cores in je applicatie, gebruik dan de multiprocessing module in plaats daarvan.

Samenvatting

  • Python ondersteunt 2 modules voor multithreading:
    1. __thread module:het biedt een implementatie op laag niveau voor threading en is verouderd.
    2. threading-module :Het biedt een implementatie op hoog niveau voor multithreading en is de huidige standaard.
  • Als u een thread wilt maken met behulp van de threading-module, moet u het volgende doen:
    1. Maak een klasse die de Thread uitbreidt klas.
    2. Overschrijf zijn constructor (__init__).
    3. Overschrijf zijn run() methode.
    4. Maak een object van deze klasse.
  • Een thread kan worden uitgevoerd door de start() . aan te roepen methode.
  • De join() methode kan worden gebruikt om andere threads te blokkeren totdat deze thread (degene waarop de join werd aangeroepen) de uitvoering voltooit.
  • Er treedt een raceconditie op wanneer meerdere threads tegelijkertijd toegang krijgen tot een gedeelde bron of deze wijzigen.
  • Het kan worden vermeden door threads te synchroniseren.
  • Python ondersteunt 6 manieren om threads te synchroniseren:
    1. Sloten
    2. Rvergrendelingen
    3. Semaforen
    4. Voorwaarden
    5. Evenementen, en
    6. Barrières
  • Locks laten alleen een bepaalde thread toe die de lock heeft gekregen om de kritieke sectie te betreden.
  • Een slot heeft 2 primaire methoden:
    1. verkrijgen() :Het stelt de vergrendelingsstatus in op vergrendeld. Als een vergrendeld object wordt aangeroepen, wordt het geblokkeerd totdat de bron vrij is.
    2. release() :Het stelt de vergrendelingsstatus in op ontgrendeld en keert terug. Als een ontgrendeld object wordt aangeroepen, retourneert het false.
  • De globale interpretervergrendeling is een mechanisme waardoor slechts 1 CPython-interpreterproces tegelijk kan worden uitgevoerd.
  • Het werd gebruikt om de functie voor het tellen van referenties van de afvalverzamelaar van CPythons te vergemakkelijken.
  • Om Python-apps te maken met zware CPU-gebonden bewerkingen, moet je de multiprocessing-module gebruiken.

Python

  1. free() Functie in C-bibliotheek:Hoe te gebruiken? Leer met voorbeeld
  2. Python String strip() Functie met VOORBEELD
  3. Python String count() met VOORBEELDEN
  4. Python round() functie met VOORBEELDEN
  5. Python map() functie met VOORBEELDEN
  6. Python Timeit() met voorbeelden
  7. Python-teller in verzamelingen met voorbeeld
  8. Python List count() met VOORBEELDEN
  9. Python Lijst index() met Voorbeeld
  10. C# - Multithreading
  11. Python - Programmeren met meerdere threads