Multithreading-in Python met Voorbeeld: Leer GIL in Python
โก Slimme samenvatting
Multithreading-in Python runs several threads inside one process so they share memory and work concurrently. The threading module creates and manages these threads, while the Global Interpreter Lock limits true parallelism, making the technique best for input/output-bound tasks.

De Python programming language allows you to use multiprocessing or multithreading. In this tutorial, you will learn how to write multithreaded applications in Python.
Wat is een draad?
A thread is a unit of execution in concurrent programming. Multithreading is a technique that allows a CPU to execute many tasks of one process at the same time. These threads can execute individually while sharing their process resources.
Wat is een proces?
A process is basically the program in execution. When you start an application on your computer (like a browser or text editor), the operating system creates a proces.
Wat is multithreading? Python?
Multithreading-in Python programming is a well-known technique in which multiple threads in a process share their data space with the main thread, which makes information sharing and communication within threads easy and efficient. Threads are lighter than processes. Multiple threads may execute individually while sharing their process resources. The purpose of multithreading is to run multiple tasks and functions at the same time.
Wat is multiprocessing?
multiprocessing stelt u in staat om meerdere ongerelateerde processen tegelijkertijd uit te voeren. Deze processen delen hun bronnen niet en communiceren via IPC.
Python Multithreading versus multiprocessing
To understand processes and threads, consider this scenario: An .exe file on your computer is a program. When you open it, the OS loads it into memory, and the CPU executes it. The instance of the program that is now running is called the process.
Every process has two fundamental components:
- De Code
- De data
Nu kan een proces een of meer zogenaamde subonderdelen bevatten threads. This depends on the OS architecture. You can think of a thread as a section of the process that can be executed separately by the operating system.
In other words, it is a stream of instructions that can be run independently by the OS. Threads within a single process share the data of that process and are designed to work together to facilitate parallelism.
Waarom multithreading gebruiken?
Met multithreading kunt u een applicatie opsplitsen in meerdere subtaken en deze taken tegelijkertijd uitvoeren. Als u multithreading op de juiste manier gebruikt, kunnen de snelheid, prestaties en rendering van uw applicatie worden verbeterd.
Python multithreading
Python supports constructs for both multiprocessing and multithreading. In this tutorial, you will primarily focus on implementing meerdradig toepassingen met Python. There are two main modules that can be used to handle threads in Python:
- De draad module, en
- De threading module
Echter, in Python, there is also something called a global interpreter lock (GIL). It does not allow for much performance gain and may even verminderen de prestaties van sommige multithreaded applicaties. Je leert er alles over in de komende secties van deze tutorial.
De modules Draad en Draadsnijden
De twee modules waarover u in deze zelfstudie leert, zijn de draadmodule en draadmodule.
De threadmodule is echter al lang verouderd. Te beginnen met Python 3, is het als verouderd aangemerkt en is het alleen toegankelijk als _thread voor achterwaartse compatibiliteit.
Je moet het hogere niveau gebruiken threading module for applications that you intend to deploy. The thread module has only been covered here for educational purposes.
De draadmodule
De syntaxis voor het maken van een nieuwe thread met 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 kladblok en typ het volgende:
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:
You will learn more about race conditions and how to handle them in the upcoming sections.
CODE-UITLEG
- These statements import the time and thread module, which are used to handle the execution and delaying of the Python threads.
- Hier hebt u een functie gedefinieerd met de naam draad_test, die zal worden gebeld door de start_nieuwe_thread method. The function runs a while loop for four iterations and prints the name of the thread that called it. Once the iteration is complete, it prints a message saying that the thread has finished execution.
- Dit is het hoofdgedeelte van je programma. Hier belt u eenvoudigweg de start_nieuwe_thread methode met de draad_test function as an argument. This will create a new thread for the function you pass as an argument and start executing it. Note that you can replace this (thread_test) with any other function that you want to run as a thread.
De draadmodule
This module is the high-level implementation of threading in Python and the de facto standard for managing multithreaded applications. It provides a wide range of features when compared to the thread module.
Structuur van de draadsnijmodule
Hier is een lijst met enkele nuttige functies die in deze module zijn gedefinieerd:
| Functie Naam | Beschrijving |
|---|---|
| actieveAantal() | Retourneert het aantal van Draad objects that are still alive. |
| huidigeDraad() | Retourneert het huidige object van de klasse Thread. |
| opsommen () | Geeft een overzicht van alle actieve Thread-objecten. |
| isDaemon() | Retourneert true als de thread een daemon is. |
| is levend() | Retourneert waar als de thread nog leeft. |
| Thread Class-methoden | |
| begin() | Start de activiteit van een thread. Het moet voor elke thread slechts รฉรฉn keer worden aangeroepen, omdat er een runtimefout ontstaat als het meerdere keren wordt aangeroepen. |
| rennen() | Deze methode geeft de activiteit van een thread aan en kan worden overschreven door een klasse die de Thread-klasse uitbreidt. |
| meedoen () | Het blokkeert de uitvoering van andere code totdat de thread waarop de methode join() werd aangeroepen, wordt beรซindigd. |
Achtergrondverhaal: de draadklasse
Before you start coding multithreaded programs using the threading module, it is crucial to understand the Thread class. The thread class is the primary class that defines the template and the operations of a thread in Python.
The most common way to create a multithreaded Python application is to declare a class that extends the Thread class and overrides its run() method.
Samenvattend betekent de Thread-klasse een codereeks die in een afzonderlijke codereeks wordt uitgevoerd draad van controle.
Wanneer u een multithreaded app schrijft, doet u het volgende:
- define a class that extends the Thread class
- Overschrijf de __in het__ aannemer
- Overschrijf de rennen() methode
Zodra een draadobject is gemaakt, wordt het begin() method can be used to begin the execution of this activity, and the meedoen () methode kan worden gebruikt om alle andere code te blokkeren totdat de huidige activiteit is voltooid.
Now, let us try using the threading module to implement your previous example. Again, fire up your IDLE en typ het volgende in:
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 uitvoer zijn wanneer u de bovenstaande code uitvoert:
CODE-UITLEG
- This part is the same as our previous example. Here, you import the time and thread module, which are used to handle the execution and delays of the Python threads.
- In dit bit maak je een klasse met de naam threadtester, die de Draad class of the threading module. This is one of the most common ways of creating threads in Python. However, you should only override the constructor and the rennen() methode in uw app. Zoals je in het bovenstaande codevoorbeeld kunt zien, is de __in het__ methode (constructor) is overschreven. Op dezelfde manier heb je ook de rennen() methode. Het bevat de code die u in een thread wilt uitvoeren. In dit voorbeeld heb je de functie thread_test() aangeroepen.
- This is the thread_test() method, which takes the value of i as an argument, decreases it by 1 at each iteration, and loops through the rest of the code until i becomes 0. In each iteration, it prints the name of the currently executing thread and sleeps for wait seconds (which is also taken as an argument).
- thread1 = threadtester(1, โEerste Threadโ, 1) Hier maken we een thread en geven we de drie parameters door die we hebben aangegeven in __init__. 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.
- thread2.start() The start method is used to start the execution of a thread. Internally, the start() function calls the run() method of your class.
- thread3.join() De methode join() blokkeert de uitvoering van andere code en wacht tot de thread waarop deze is aangeroepen, is voltooid.
As you already know, the threads that are in the same process have access to the memory and data of that process. As a result, if more than one thread tries to change or access the data simultaneously, errors may creep in.
In the next section, you will see the different kinds of complications that can show up when threads access data and the critical section without checking for existing access transactions.
Deadlocks en raceomstandigheden
Before learning about deadlocks and race conditions, it will be helpful to understand a few basic definitions related to concurrent programming:
- Kritieke sectie: It is a fragment of code that accesses or modifies shared variables and must be performed as an atomic transaction.
- Contextwisseling: It is the process that a CPU follows to store the state of a thread before changing from one task to another so that it can be resumed from the same point later.
impasses
impasses are the most feared issue that developers face when writing concurrent/multithreaded applications in Python. The best way to understand deadlocks is by using the classic computer science example problem known as the Dineren Philosofers Probleem.
De probleemstelling voor eetfilosofen luidt als volgt:
Five philosophers are seated at a round table with five plates of spaghetti (a type of pasta) and five forks, as shown in the diagram.
Dineren Philosofers Probleem
Een filosoof moet op elk willekeurig moment รณf eten รณf denken.
Bovendien moet een filosoof de twee vorken die naast hem liggen (d.w.z. de linker- en rechtervork) pakken voordat hij de spaghetti kan eten. Het probleem van deadlock doet zich voor wanneer alle vijf filosofen tegelijkertijd hun rechtervork oppakken.
Omdat elke filosofen รฉรฉn vork heeft, zullen ze allemaal wachten tot de anderen hun vork neerleggen. Als gevolg hiervan zal niemand van hen spaghetti kunnen eten.
Op dezelfde manier treedt er in een gelijktijdig systeem een โโdeadlock op wanneer verschillende threads of processen (philosophers) 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.
Race voorwaarden
A race condition is an unwanted state of a program that occurs when a system performs two or more operations simultaneously. For example, consider this simple for loop:
i=0; # a global variable for x in range(100): print(i) i+=1;
Als je creรซert n number of threads that run this code at once, you cannot determine the value of i (which is shared by the threads) when the program finishes execution. This is because in a real multithreading environment, the threads can overlap, and the value of i that was retrieved and modified by a thread can change in between when some other thread accesses it.
These are the two main classes of problems that can occur in a multithreaded or distributed Python application. In the next section, you will learn how to overcome this problem by synchronizing threads.
Syncroniserende draden
Om met race-omstandigheden, deadlocks en andere thread-gebaseerde problemen om te gaan, biedt de threading-module de volgende mogelijkheden: Slot object. Het idee is dat wanneer een thread toegang wil tot een specifieke resource, deze een lock voor die resource verkrijgt. Zodra een thread een bepaalde resource lockt, kan geen enkele andere thread er toegang toe krijgen totdat de lock wordt vrijgegeven. Als gevolg hiervan zullen de wijzigingen aan de resource atomisch zijn en worden racecondities afgewend.
Een slot is een synchronisatieprimitief op laag niveau dat wordt geรฏmplementeerd door de _thread module. At any given time, a lock can be in one of two states: opgesloten or ontgrendeld. Het ondersteunt twee methoden:
- acquire(): When the lock state is unlocked, calling the acquire() method will change the state to locked and return. However, if the state is locked, the call to acquire() is blocked until the release() method is called by some other thread.
- release(): De release()-methode wordt gebruikt om de status op ontgrendeld te zetten, dat wil zeggen om een โโvergrendeling op te heffen. Het kan door elke thread worden aangeroepen, niet noodzakelijkerwijs degene die het slot heeft verkregen.
Here is an example of using locks in your apps. Fire up your IDLE 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
- Hier maakt u eenvoudigweg een nieuw slot aan door de draadsnijden.Lock() fabrieksfunctie. Intern retourneert Lock() een instantie van de meest effectieve concrete Lock-klasse die door het platform wordt onderhouden.
- In de eerste instructie verkrijgt u het slot door de methode acquire() aan te roepen. Wanneer de vergrendeling is verleend, drukt u af โslot verworvenโ naar de console. Zodra alle code die u door de thread wilt laten uitvoeren, is uitgevoerd, geeft u de vergrendeling vrij door de methode release() aan te roepen.
The theory is fine, but how do you know that the lock really worked? If you look at the output, you will see that each of the print statements is printing exactly one line at a time. Recall that, in an earlier example, the outputs from print were haphazard because multiple threads were accessing the print() method at the same time. Here, the print function is called only after the lock is acquired. So, the outputs are displayed one at a time and line by line.
Apart from locks, Python also supports some other mechanisms to handle thread synchronization, as listed below:
- RSloten
- Semaphores
- Voorwaarden
- Evenementen, en
- Barriรจres
Global Interpreter Lock (en hoe hiermee om te gaan)
Voordat we ingaan op de details van Pythonโs GIL, let us define a few terms that will be useful in understanding the upcoming section:
- CPU-bound code: this refers to any piece of code that will be directly executed by the CPU.
- I/O-bound code: this can be any code that accesses the file system through the OS.
- CPython: het is de referentie uitvoering of Python en kan worden omschreven als de tolk geschreven in C en Python (programmeertaal).
Waar zit GIL in? Python?
Wereldwijd tolkslot (GIL) in Python is a process lock or a mutex used while dealing with the processes. It makes sure that one thread can access a particular resource at a time, and it also prevents the use of objects and bytecodes at once. This benefits the single-threaded programs with a performance increase. GIL in Python is very simple and easy to implement.
Een vergrendeling kan worden gebruikt om ervoor te zorgen dat slechts รฉรฉn thread op een bepaald moment toegang heeft tot een bepaalde bron.
Een van de kenmerken van Python is that it uses a global lock on each interpreter process, which means that every process treats the Python interpreter itself as a resource.
For example, suppose you have written a Python program that uses two threads to perform both CPU and โI/Oโ operations. When you execute this program, this is what happens:
- De Python interpreter creates a new process and spawns the threads.
- Wanneer thread-1 begint te lopen, zal het eerst de GIL verkrijgen en vergrendelen.
- Als thread-2 nu wil uitvoeren, zal het moeten wachten tot de GIL wordt vrijgegeven, zelfs als er een andere processor vrij is.
- Stel nu dat thread-1 wacht op een I/O-bewerking. Op dat moment zal het de GIL vrijgeven en thread-2 zal deze verkrijgen.
- Als thread-1 na het voltooien van de I/O-bewerkingen nu wil worden uitgevoerd, zal het opnieuw moeten wachten tot de GIL door thread-2 wordt vrijgegeven.
Due to this, only one thread can access the interpreter at any time, meaning that there will be only one thread executing Python code at a given point in time.
This is alright in a single-core processor because it would be using time slicing (see the first section of this tutorial) to handle the threads. However, in the case of multi-core processors, a CPU-bound function executing on multiple threads will have a considerable impact on the programโs efficiency since it will not actually be using all the available cores at the same time.
Waarom was GIL nodig?
de CPython garbage collector uses an efficient memory management technique known as reference counting. Here is how it works: Every object in Python has a reference count, which is increased when it is assigned to a new variable name or added to a container (like tuples, lists, etc.). Likewise, the reference count is decreased when the reference goes out of scope or when the del statement is called. When the reference count of an object reaches 0, it is garbage collected, and the allotted memory is freed.
But the problem is that the reference count variable is prone to race conditions like any other global variable. To solve this problem, the developers of Python decided to use the global interpreter lock. The other option was to add a lock to each object, which would have resulted in deadlocks and increased overhead from acquire() and release() calls.
Therefore, GIL is a significant restriction for multithreaded Python programs running heavy CPU-bound operations (effectively making them single-threaded). If you want to make use of multiple CPU cores in your application, use the multiverwerking module in plaats daarvan.








