Multithreading em Python com exemplo: Aprenda GIL em Python
โก Resumo Inteligente
Multithreading em 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.

O Python programming language allows you to use multiprocessing or multithreading. In this tutorial, you will learn how to write multithreaded applications in Python.
O que รฉ um fio?
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.
O que รฉ um Processo?
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 processo.
O que รฉ multithreading Python?
Multithreading em 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.
O que รฉ multiprocessamento?
Multiprocessamento permite que vocรช execute vรกrios processos nรฃo relacionados simultaneamente. Esses processos nรฃo compartilham seus recursos e se comunicam atravรฉs do IPC.
Python Multithreading x Multiprocessamento
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:
- O Code
- Os Dados
Agora, um processo pode conter uma ou mais subpartes chamadas tรณpicos. 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.
Por que usar multithreading?
Multithreading permite dividir um aplicativo em vรกrias subtarefas e executar essas tarefas simultaneamente. Se vocรช usar o multithreading corretamente, a velocidade, o desempenho e a renderizaรงรฃo do seu aplicativo poderรฃo ser melhorados.
Python multithreading
Python supports constructs for both multiprocessing and multithreading. In this tutorial, you will primarily focus on implementing multithread aplicaรงรตes com Python. There are two main modules that can be used to handle threads in Python:
- O fio mรณdulo, e
- O segmentaรงรฃo mรณdulo
No entanto, em Python, there is also something called a global interpreter lock (GIL). It does not allow for much performance gain and may even reduzir o desempenho de alguns aplicativos multithread. Vocรช aprenderรก tudo sobre isso nas prรณximas seรงรตes deste tutorial.
Os mรณdulos Thread e Threading
Os dois mรณdulos que vocรช aprenderรก neste tutorial sรฃo o mรณdulo de thread e mรณdulo de threading.
No entanto, o mรณdulo thread estรก obsoleto hรก muito tempo. Comeรงando com Python 3, foi designado como obsoleto e sรณ รฉ acessรญvel como _fio para compatibilidade com versรตes anteriores.
Vocรช deve usar o nรญvel superior segmentaรงรฃo module for applications that you intend to deploy. The thread module has only been covered here for educational purposes.
O Mรณdulo de Thread
A sintaxe para criar um novo thread usando este mรณdulo รฉ a seguinte:
thread.start_new_thread(function_name, arguments)
Tudo bem, agora vocรช cobriu a teoria bรกsica para comeรงar a codificar. Entรฃo, abra seu IDLE ou um bloco de notas e digite o seguinte:
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))
Salve o arquivo e pressione F5 para executar o programa. Se tudo foi feito corretamente, esta รฉ a saรญda que vocรช deverรก ver:
You will learn more about race conditions and how to handle them in the upcoming sections.
EXPLICAรรO DO CรDIGO
- These statements import the time and thread module, which are used to handle the execution and delaying of the Python tรณpicos.
- Aqui, vocรช definiu uma funรงรฃo chamada thread_teste, que serรก chamado pelo start_new_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.
- Esta รฉ a seรงรฃo principal do seu programa. Aqui, basta ligar para o start_new_thread mรฉtodo com o thread_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.
O Mรณdulo de Threading
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.
Estrutura do mรณdulo Threading
Aqui estรก uma lista de algumas funรงรตes รบteis definidas neste mรณdulo:
| Nome da Funรงรฃo | Descriรงรฃo |
|---|---|
| contagem ativa() | Retorna a contagem de Fio objects that are still alive. |
| currentThread () | Retorna o objeto atual da classe Thread. |
| enumerar() | Lista todos os objetos Thread ativos. |
| isDaemon() | Retorna verdadeiro se o thread for um daemon. |
| Estรก vivo() | Retorna verdadeiro se o thread ainda estiver ativo. |
| Mรฉtodos de classe de thread | |
| comeรงar() | Inicia a atividade de um thread. Ele deve ser chamado apenas uma vez para cada thread porque gerarรก um erro de tempo de execuรงรฃo se for chamado vรกrias vezes. |
| corre() | Este mรฉtodo denota a atividade de um thread e pode ser substituรญdo por uma classe que estende a classe Thread. |
| Junte-se() | Ele bloqueia a execuรงรฃo de outro cรณdigo atรฉ que o thread no qual o mรฉtodo join() foi chamado seja encerrado. |
Histรณria de fundo: a classe 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.
A classe Thread, em resumo, significa uma sequรชncia de cรณdigo que รฉ executada em um fio de controle.
Portanto, ao escrever um aplicativo multithread, vocรช farรก o seguinte:
- define a class that extends the Thread class
- Substituir o __init__ construtor
- Substituir o corre() mรฉtodo
Depois que um objeto thread for criado, o comeรงar() method can be used to begin the execution of this activity, and the Junte-se() O mรฉtodo pode ser usado para bloquear todos os outros cรณdigos atรฉ que a atividade atual termine.
Now, let us try using the threading module to implement your previous example. Again, fire up your IDLE e digite o seguinte:
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()
Esta serรก a saรญda quando vocรช executar o cรณdigo acima:
EXPLICAรรO DO CรDIGO
- 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 tรณpicos.
- Nesta parte, vocรช estรก criando uma classe chamada threadtester, que herda ou estende o Fio 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 corre() mรฉtodo em seu aplicativo. Como vocรช pode ver no exemplo de cรณdigo acima, o __init__ o mรฉtodo (construtor) foi substituรญdo. Da mesma forma, vocรช tambรฉm substituiu o corre() mรฉtodo. Ele contรฉm o cรณdigo que vocรช deseja executar dentro de um thread. Neste exemplo, vocรช chamou a funรงรฃo 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, โFirst Threadโ, 1) Aqui estamos criando uma thread e passando os trรชs parรขmetros que declaramos em __init__. O primeiro parรขmetro รฉ o id do thread, o segundo parรขmetro รฉ o nome do thread e o terceiro parรขmetro รฉ o contador, que determina quantas vezes o loop while deve ser executado.
- 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() O mรฉtodo join() bloqueia a execuรงรฃo de outro cรณdigo e espera atรฉ que o thread em que foi chamado termine.
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.
Impasses e condiรงรตes de corrida
Before learning about deadlocks and race conditions, it will be helpful to understand a few basic definitions related to concurrent programming:
- Seรงรฃo Crรญtica: It is a fragment of code that accesses or modifies shared variables and must be performed as an atomic transaction.
- Troca de contexto: 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 Para Refeiรงรตes PhiloProblema de Sopher.
A definiรงรฃo do problema para os filรณsofos do jantar รฉ a seguinte:
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.
Para Refeiรงรตes PhiloProblema de Sophers
A qualquer momento, um filรณsofo deve estar comendo ou pensando.
Alรฉm disso, um filรณsofo deve pegar os dois garfos adjacentes a ele (isto รฉ, os garfos esquerdo e direito) antes de poder comer o espaguete. O problema do impasse ocorre quando todos os cinco filรณsofos escolhem os garfos certos simultaneamente.
Como cada um dos filรณsofos tem um garfo, todos esperarรฃo que os outros pousem o garfo. Como resultado, nenhum deles poderรก comer espaguete.
Da mesma forma, em um sistema simultรขneo, ocorre um impasse quando diferentes threads ou processos (filรณsofos) tentam adquirir os recursos compartilhados do sistema (forks) ao mesmo tempo. Como resultado, nenhum dos processos tem chance de ser executado, pois estรฃo aguardando outro recurso mantido por algum outro processo.
Condiรงรตes da corrida
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;
Se vocรช criar 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.
Synccronizando tรณpicos
Para lidar com condiรงรตes de corrida, impasses e outros problemas baseados em thread, o mรณdulo threading fornece o Travar objeto. A ideia รฉ que quando uma thread deseja acessar um recurso especรญfico, ela adquira um bloqueio para esse recurso. Depois que um thread bloqueia um recurso especรญfico, nenhum outro thread pode acessรก-lo atรฉ que o bloqueio seja liberado. Como resultado, as alteraรงรตes no recurso serรฃo atรดmicas e as condiรงรตes de corrida serรฃo evitadas.
Um bloqueio รฉ uma primitiva de sincronizaรงรฃo de baixo nรญvel implementada pelo _fio module. At any given time, a lock can be in one of two states: trancado or desbloqueado. Ele suporta dois mรฉtodos:
- 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(): O mรฉtodo release() รฉ usado para definir o estado como desbloqueado, ou seja, para liberar um bloqueio. Pode ser chamado por qualquer thread, nรฃo necessariamente aquela que adquiriu o bloqueio.
Here is an example of using locks in your apps. Fire up your IDLE e digite o seguinte:
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()
Agora, aperte F5. Vocรช deverรก ver uma saรญda como esta:
EXPLICAรรO DO CรDIGO
- Aqui, vocรช estรก simplesmente criando um novo bloqueio chamando o threading.Lock () funรงรฃo de fรกbrica. Internamente, Lock() retorna uma instรขncia da classe Lock concreta mais eficaz que รฉ mantida pela plataforma.
- Na primeira instruรงรฃo, vocรช adquire o bloqueio chamando o mรฉtodo adquirir(). Quando o bloqueio for concedido, vocรช imprime โbloqueio adquiridoโ para o console. Depois que todo o cรณdigo que vocรช deseja que o thread execute tenha concluรญdo a execuรงรฃo, vocรช libera o bloqueio chamando o mรฉtodo 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:
- RLocks
- Semaphores
- Condiรงรตes
- Eventos, e
- Barreiras
Bloqueio global de intรฉrprete (e como lidar com isso)
Antes de entrar nos detalhes 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: รฉ a referรชncia implementaรงรฃo of Python e pode ser descrito como o intรฉrprete escrito em C e Python (linguagem de programaรงรฃo).
Em que estรก o GIL Python?
Bloqueio global de intรฉrprete (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.
Um bloqueio pode ser usado para garantir que apenas um thread tenha acesso a um recurso especรญfico em um determinado momento.
Uma das caracterรญsticas do 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:
- O Python interpreter creates a new process and spawns the threads.
- Quando o thread-1 comeรงar a ser executado, ele primeiro adquirirรก o GIL e o bloquearรก.
- Se o thread-2 quiser executar agora, ele terรก que esperar a liberaรงรฃo do GIL, mesmo que outro processador esteja livre.
- Agora, suponha que o thread-1 esteja aguardando uma operaรงรฃo de E/S. Neste momento, ele irรก liberar o GIL e o thread-2 irรก adquiri-lo.
- Depois de concluir as operaรงรตes de E/S, se o thread-1 quiser executar agora, ele terรก que esperar novamente que o GIL seja liberado pelo thread-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.
Por que o GIL foi necessรกrio?
O 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 multiprocessamento mรณdulo em vez disso.








