Wielowątkowość w Python z przykładem: Naucz się GIL w Python
⚡ Inteligentne podsumowanie
Wielowątkowość w 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.

Python programming language allows you to use multiprocessing or multithreading. In this tutorial, you will learn how to write multithreaded applications in Python.
Co to jest wątek?
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.
Czym jest 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.
Na czym polega wielowątkowość Python?
Wielowątkowość w 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.
Co to jest przetwarzanie wieloprocesowe?
Wieloprocesowe pozwala na jednoczesne uruchomienie wielu niezwiązanych ze sobą procesów. Procesy te nie współdzielą swoich zasobów i komunikują się poprzez IPC.
Python Wielowątkowość a wieloprocesowość
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:
- Code
- Dane
Teraz proces może zawierać jedną lub więcej podczęści zwanych wątki 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.
Dlaczego warto używać wielowątkowości?
Wielowątkowość umożliwia podzielenie aplikacji na wiele podzadań i jednoczesne uruchamianie tych zadań. Jeśli prawidłowo użyjesz wielowątkowości, możesz poprawić szybkość, wydajność i renderowanie aplikacji.
Python Wielowątkowość
Python supports constructs for both multiprocessing and multithreading. In this tutorial, you will primarily focus on implementing wielowątkowy aplikacje z Python. There are two main modules that can be used to handle threads in Python:
- wątek moduł i
- gwintowanie moduł
Jednak w Python, there is also something called a global interpreter lock (GIL). It does not allow for much performance gain and may even zmniejszyć wydajność niektórych aplikacji wielowątkowych. Wszystkiego dowiesz się w kolejnych rozdziałach tego poradnika.
Moduły Thread i Threading
Dwa moduły, o których dowiesz się w tym samouczku, to moduł wątku i moduł gwintowania.
Jednakże moduł wątku jest od dawna przestarzały. Zaczynając od Python 3, został on oznaczony jako przestarzały i jest dostępny jedynie jako _thread dla kompatybilności wstecznej.
Powinieneś użyć wyższego poziomu gwintowanie module for applications that you intend to deploy. The thread module has only been covered here for educational purposes.
Moduł wątku
Składnia tworzenia nowego wątku za pomocą tego modułu jest następująca:
thread.start_new_thread(function_name, arguments)
W porządku, teraz omówiłeś podstawową teorię dotyczącą rozpoczęcia kodowania. Więc otwórz swój IDLE lub notatnik i wpisz następujące informacje:
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))
Zapisz plik i naciśnij klawisz F5, aby uruchomić program. Jeśli wszystko zostało wykonane poprawnie, powinieneś zobaczyć następujący wynik:
You will learn more about race conditions and how to handle them in the upcoming sections.
WYJAŚNIENIE KODU
- These statements import the time and thread module, which are used to handle the execution and delaying of the Python wątki
- Tutaj zdefiniowałeś funkcję o nazwie test_wątku, który zostanie wywołany przez start_nowy_wątek 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.
- To jest główna część Twojego programu. Tutaj wystarczy zadzwonić do start_nowy_wątek metoda z test_wątku 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.
Moduł gwintowania
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.
Struktura modułu Threading
Oto lista niektórych przydatnych funkcji zdefiniowanych w tym module:
| Nazwa funkcji | OPIS |
|---|---|
| liczba aktywnych() | Zwraca liczbę Wątek objects that are still alive. |
| bieżący wątek() | Zwraca bieżący obiekt klasy Thread. |
| wyliczać() | Wyświetla listę wszystkich aktywnych obiektów Thread. |
| isDaemon() | Zwraca wartość true, jeśli wątek jest demonem. |
| żyje() | Zwraca wartość true, jeśli wątek jest nadal aktywny. |
| Metody klas wątków | |
| początek() | Rozpoczyna aktywność wątku. Należy go wywołać tylko raz dla każdego wątku, ponieważ wielokrotne wywołanie spowoduje błąd wykonania. |
| biegać() | Metoda ta oznacza aktywność wątku i może zostać przesłonięta przez klasę rozszerzającą klasę Thread. |
| Przystąp() | Blokuje wykonanie innego kodu do czasu zakończenia wątku, w którym wywołano metodę Join(). |
Historia: Klasa Thread
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.
Podsumowując, klasa Thread oznacza sekwencję kodu działającą w osobnym pliku wątek kontroli.
Pisząc aplikację wielowątkową, należy wykonać następujące czynności:
- define a class that extends the Thread class
- Zastąp __init__ konstruktor
- Zastąp biegać() metoda
Po utworzeniu obiektu wątku, początek() method can be used to begin the execution of this activity, and the Przystąp() metody można użyć do zablokowania całego innego kodu do czasu zakończenia bieżącego działania.
Now, let us try using the threading module to implement your previous example. Again, fire up your IDLE i wpisz:
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()
To będzie wynik po wykonaniu powyższego kodu:
WYJAŚNIENIE KODU
- 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 wątki
- W tym fragmencie tworzysz klasę o nazwie tester wątków, która dziedziczy lub rozszerza Wątek 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 biegać() metoda w Twojej aplikacji. Jak widać w powyższym przykładzie kodu, plik __init__ metoda (konstruktor) została nadpisana. Podobnie nadpisałeś także plik biegać() metoda. Zawiera kod, który chcesz wykonać wewnątrz wątku. W tym przykładzie wywołałeś funkcję thread_test().
- 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, „Pierwszy wątek”, 1) Tutaj tworzymy wątek i przekazujemy trzy parametry, które zadeklarowaliśmy w __init__. Pierwszy parametr to identyfikator wątku, drugi parametr to nazwa wątku, a trzeci parametr to licznik, który określa, ile razy powinna zostać wykonana pętla while.
- 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() Metoda Join() blokuje wykonanie innego kodu i czeka aż zakończy się wątek, w którym została wywołana.
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.
Blokady i warunki wyścigu
Before learning about deadlocks and race conditions, it will be helpful to understand a few basic definitions related to concurrent programming:
- Krytyczny fragment: It is a fragment of code that accesses or modifies shared variables and must be performed as an atomic transaction.
- Zmiana kontekstu: 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.
Zakleszczenia
Zakleszczenia 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 Wyżywienie PhiloSophers Problem.
Problem dla filozofów-kulinarnych jest następujący:
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.
Wyżywienie PhiloSophers Problem
Filozof w dowolnym momencie musi albo jeść, albo myśleć.
Co więcej, filozof musi wziąć dwa widelce sąsiadujące z nim (tj. lewy i prawy widelec), zanim będzie mógł zjeść spaghetti. Problem impasu pojawia się, gdy wszyscy pięciu filozofów bierze jednocześnie swoje prawe widelce.
Ponieważ każdy z filozofów ma jeden widelec, wszyscy będą czekać, aż inni odłożą widelec. W rezultacie żaden z nich nie będzie mógł zjeść spaghetti.
Podobnie w systemie współbieżnym, impas występuje, gdy różne wątki lub procesy (filozofowie) próbują pozyskać współdzielone zasoby systemowe (forki) w tym samym czasie. W rezultacie żaden z procesów nie ma szansy na wykonanie, ponieważ czekają na inny zasób przechowywany przez inny proces.
Warunki wyścigu
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;
Jeśli tworzysz 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.
Synchronizujące wątki
Aby poradzić sobie z warunkami wyścigu, blokadami i innymi problemami związanymi z wątkami, moduł wątków zapewnia Zablokować obiekt. Pomysł polega na tym, że gdy wątek chce uzyskać dostęp do określonego zasobu, uzyskuje blokadę dla tego zasobu. Gdy wątek zablokuje określony zasób, żaden inny wątek nie będzie mógł uzyskać do niego dostępu, dopóki blokada nie zostanie zwolniona. W rezultacie zmiany w zasobie będą atomowe, a warunki wyścigu zostaną zażegnane.
Blokada to prymitywna synchronizacja niskiego poziomu implementowana przez _thread module. At any given time, a lock can be in one of two states: zamknięty or odblokowany. Obsługuje dwie metody:
- 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(): Metoda release() służy do ustawienia stanu na odblokowany, czyli do zwolnienia blokady. Można go wywołać dowolnym wątkiem, niekoniecznie tym, który uzyskał blokadę.
Here is an example of using locks in your apps. Fire up your IDLE i wpisz następujące polecenie:
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()
Teraz naciśnij F5. Powinieneś zobaczyć wynik taki jak ten:
WYJAŚNIENIE KODU
- Tutaj po prostu tworzysz nową blokadę, wywołując metodę gwintowanie. Zablokuj() funkcja fabryczna. Wewnętrznie Lock() zwraca instancję najskuteczniejszej konkretnej klasy Lock obsługiwanej przez platformę.
- W pierwszej instrukcji blokadę uzyskujesz wywołując metodę nabycia(). Po przyznaniu blokady drukujesz „zamek zdobyty” do konsoli. Po zakończeniu wykonywania całego kodu, który ma zostać uruchomiony przez wątek, zwalniasz blokadę, wywołując metodę release().
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:
- RZamki
- Semaphores
- Warunki
- Wydarzenia i
- Bariery
Globalna blokada tłumacza (i jak sobie z tym poradzić)
Zanim przejdziemy do szczegółów 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: to jest odniesienie realizacja of Python i można go opisać jako interpreter napisany w C i Python (język programowania).
W czym jest GIL Python?
Globalna blokada tłumacza (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.
Blokadę można zastosować, aby mieć pewność, że w danym momencie tylko jeden wątek ma dostęp do określonego zasobu.
Jedna z cech 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:
- Python interpreter creates a new process and spawns the threads.
- Kiedy wątek-1 zacznie działać, najpierw uzyska GIL i zablokuje go.
- Jeśli wątek-2 chce teraz wykonać zadanie, będzie musiał poczekać na zwolnienie GIL, nawet jeśli inny procesor będzie wolny.
- Załóżmy teraz, że wątek 1 czeka na operację wejścia/wyjścia. W tym momencie zwolni GIL, a wątek 2 go przejmie.
- Jeśli po zakończeniu operacji we/wy wątek-1 chce się teraz wykonać, będzie musiał ponownie poczekać, aż GIL zostanie zwolniony przez wątek-2.
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.
Dlaczego GIL był potrzebny?
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 wieloprocesowe zamiast tego moduł.








