Multithreading em Python com exemplo: Aprenda GIL em Python

A linguagem de programaรงรฃo python permite que vocรช use multiprocessamento ou multithreading. Neste tutorial, vocรช aprenderรก como escrever aplicativos multithreaded em Python.

O que รฉ um fio?

Um thread รฉ uma unidade de execuรงรฃo em programaรงรฃo simultรขnea. Multithreading รฉ uma tรฉcnica que permite que uma CPU execute muitas tarefas de um processo ao mesmo tempo. Esses threads podem ser executados individualmente enquanto compartilham seus recursos de processo.

O que รฉ um Processo?

Um processo รฉ basicamente o programa em execuรงรฃo. Quando vocรช inicia um aplicativo no seu computador (como um navegador ou editor de texto), o sistema operacional cria um processo.

O que รฉ multithreading Python?

Multithreading em Python a programaรงรฃo รฉ uma tรฉcnica bem conhecida na qual vรกrios threads em um processo compartilham seu espaรงo de dados com o thread principal, o que torna o compartilhamento de informaรงรตes e a comunicaรงรฃo dentro dos threads fรกcil e eficiente. Threads sรฃo mais leves que processos. Multi threads podem ser executados individualmente enquanto compartilham seus recursos de processo. O objetivo do multithreading รฉ executar vรกrias tarefas e cรฉlulas funcionais ao mesmo tempo.

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

Para entender processos e threads, considere este cenรกrio: Um arquivo .exe no seu computador รฉ um programa. Ao abri-lo, o sistema operacional carrega-o na memรณria e a CPU o executa. A instรขncia do programa que estรก sendo executada agora รฉ chamada de processo.

Cada processo terรก 2 componentes fundamentais:

  • O cรณdigo
  • Os Dados

Agora, um processo pode conter uma ou mais subpartes chamadas tรณpicos. Isso depende da arquitetura do sistema operacional. Vocรช pode pensar em um thread como uma seรงรฃo do processo que pode ser executada separadamente pelo sistema operacional.

Em outras palavras, รฉ um fluxo de instruรงรตes que pode ser executado de forma independente pelo sistema operacional. Threads dentro de um รบnico processo compartilham os dados desse processo e sรฃo projetados para trabalhar juntos para facilitar o paralelismo.

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 suporta construรงรตes tanto para multiprocessamento quanto para multithreading. Neste tutorial, vocรช se concentrarรก principalmente na implementaรงรฃo multithread aplicaรงรตes com python. Existem dois mรณdulos principais que podem ser usados โ€‹โ€‹para manipular threads em Python:

  1. O processo de fio mรณdulo, e
  2. O processo de segmentaรงรฃo mรณdulo

No entanto, em python, tambรฉm existe algo chamado bloqueio de interpretador global (GIL). Nรฃo permite muito ganho de desempenho e pode atรฉ 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 mรณdulo para aplicativos que vocรช pretende implantar. O mรณdulo thread foi abordado aqui apenas para fins educacionais.

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:

O Mรณdulo de Thread

Vocรช aprenderรก mais sobre as condiรงรตes de corrida e como lidar com elas nas prรณximas seรงรตes.

O Mรณdulo de Thread

EXPLICAร‡รƒO DO Cร“DIGO

  1. Essas instruรงรตes importam o mรณdulo de tempo e thread que sรฃo usados โ€‹โ€‹para lidar com a execuรงรฃo e o atraso do Python tรณpicos.
  2. Aqui, vocรช definiu uma funรงรฃo chamada thread_teste, que serรก chamado pelo start_new_thread mรฉtodo. A funรงรฃo executa um loop while por quatro iteraรงรตes e imprime o nome do thread que a chamou. Assim que a iteraรงรฃo for concluรญda, ele imprime uma mensagem informando que o thread terminou a execuรงรฃo.
  3. Esta รฉ a seรงรฃo principal do seu programa. Aqui, basta ligar para o start_new_thread mรฉtodo com o thread_test funcionar como um argumento. Isso criarรก um novo thread para a funรงรฃo que vocรช passa como argumento e comeรงarรก a executรก-la. Observe que vocรช pode substituir isso (thread_test) com qualquer outra funรงรฃo que vocรช deseja executar como um thread.

O Mรณdulo de Threading

Este mรณdulo รฉ a implementaรงรฃo de alto nรญvel de threading em python e o padrรฃo de fato para gerenciamento de aplicativos multithread. Ele fornece uma ampla gama de recursos quando comparado ao mรณdulo de thread.

Estrutura do mรณdulo Threading
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 objetos que ainda estรฃo vivos
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

Antes de comeรงar a codificar programas multithread usando o mรณdulo threading, รฉ crucial entender sobre a classe Thread. A classe thread รฉ a classe primรกria que define o modelo e as operaรงรตes de um thread em python.

A maneira mais comum de criar um aplicativo python multithread รฉ declarar uma classe que estende a classe Thread e substitui seu mรฉtodo run().

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:

  1. defina uma classe que estenda a classe Thread
  2. Substituir o __init__ construtor
  3. Substituir o corre() mรฉtodo

Depois que um objeto thread for criado, o comeรงar() mรฉtodo pode ser usado para iniciar a execuรงรฃo desta atividade e o Junte-se() O mรฉtodo pode ser usado para bloquear todos os outros cรณdigos atรฉ que a atividade atual termine.

Agora, vamos tentar usar o mรณdulo threading para implementar seu exemplo anterior. Mais uma vez, acenda seu 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:

Histรณria de fundo: a classe Thread

EXPLICAร‡รƒO DO Cร“DIGO

Histรณria de fundo: a classe Thread

  1. Esta parte รฉ a mesma do nosso exemplo anterior. Aqui, vocรช importa o mรณdulo time e thread que sรฃo usados โ€‹โ€‹para manipular a execuรงรฃo e os atrasos do Python tรณpicos.
  2. Nesta parte, vocรช estรก criando uma classe chamada threadtester, que herda ou estende o Fio classe do mรณdulo de threading. Esta รฉ uma das formas mais comuns de criar threads em python. No entanto, vocรช sรณ deve substituir o construtor e o 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().
  3. Este รฉ o mรฉtodo thread_test() que assume o valor de i como argumento, diminui em 1 a cada iteraรงรฃo e percorre o restante do cรณdigo atรฉ que i se torne 0. Em cada iteraรงรฃo, ele imprime o nome do thread em execuรงรฃo no momento e dorme por alguns segundos (o que tambรฉm รฉ considerado um argumento ).
  4. 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.
  5. thread2.start()O mรฉtodo start รฉ usado para iniciar a execuรงรฃo de um thread. Internamente, a funรงรฃo start() chama o mรฉtodo run() da sua classe.
  6. 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.

Como vocรช jรก sabe, as threads que estรฃo no mesmo processo tรชm acesso ร  memรณria e aos dados desse processo. Como resultado, se mais de um thread tentar alterar ou acessar os dados simultaneamente, poderรฃo ocorrer erros.

Na prรณxima seรงรฃo, vocรช verรก os diferentes tipos de complicaรงรตes que podem surgir quando threads acessam dados e seรงรตes crรญticas sem verificar as transaรงรตes de acesso existentes.

Impasses e condiรงรตes de corrida

Antes de aprender sobre impasses e condiรงรตes de corrida, serรก รบtil entender algumas definiรงรตes bรกsicas relacionadas ร  programaรงรฃo simultรขnea:

  • Seรงรฃo Crรญticaร‰ um fragmento de cรณdigo que acessa ou modifica variรกveis โ€‹โ€‹compartilhadas e deve ser executado como uma transaรงรฃo atรดmica.
  • Context SwitchIt รฉ o processo que uma CPU segue para armazenar o estado de um thread antes de mudar de uma tarefa para outra, para que possa ser retomada do mesmo ponto posteriormente.

Impasses

Impasses sรฃo o problema mais temido que os desenvolvedores enfrentam ao escrever aplicativos simultรขneos/multithread em python. A melhor maneira de entender os impasses รฉ usar o problema clรกssico de exemplo da ciรชncia da computaรงรฃo conhecido como Para Refeiรงรตes PhiloProblema de Sopher.

A definiรงรฃo do problema para os filรณsofos do jantar รฉ a seguinte:

Cinco filรณsofos estรฃo sentados numa mesa redonda com cinco pratos de espaguete (uma espรฉcie de macarrรฃo) e cinco garfos, conforme mostra o diagrama.

Para Refeiรงรตes PhiloProblema de Sophers

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

Uma condiรงรฃo de corrida รฉ um estado indesejado de um programa que ocorre quando um sistema executa duas ou mais operaรงรตes simultaneamente. Por exemplo, considere este loop for simples:

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

Se vocรช criar n nรบmero de threads que executam este cรณdigo de uma vez, vocรช nรฃo pode determinar o valor de i (que รฉ compartilhado pelos threads) quando o programa termina a execuรงรฃo. Isso ocorre porque em um ambiente multithreading real, os threads podem se sobrepor, e o valor de i que foi recuperado e modificado por um thread pode mudar quando algum outro thread o acessa.

Estas sรฃo as duas principais classes de problemas que podem ocorrer em um aplicativo Python multithread ou distribuรญdo. Na prรณxima seรงรฃo, vocรช aprenderรก como superar esse problema sincronizando 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 mรณdulo. A qualquer momento, um bloqueio pode estar em um dos 2 estados: trancado or desbloqueado. Ele suporta dois mรฉtodos:

  1. adquirir()Quando o estado de bloqueio รฉ desbloqueado, chamar o mรฉtodo adquirir() mudarรก o estado para bloqueado e retornarรก. No entanto, se o estado estiver bloqueado, a chamada para adquirir() serรก bloqueada atรฉ que o mรฉtodo release() seja chamado por algum outro thread.
  2. liberar()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.

Aqui estรก um exemplo de uso de bloqueios em seus aplicativos. Acenda seu 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:

Synccronizando Threads

EXPLICAร‡รƒO DO Cร“DIGO

Synccronizando Threads

  1. 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.
  2. 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().

A teoria รฉ boa, mas como saber se a fechadura realmente funcionou? Se vocรช observar a saรญda, verรก que cada uma das instruรงรตes print estรก imprimindo exatamente uma linha por vez. Lembre-se de que, em um exemplo anterior, as saรญdas de print eram aleatรณrias porque vรกrios threads estavam acessando o mรฉtodo print() ao mesmo tempo. Aqui, a funรงรฃo print รฉ chamada somente apรณs o bloqueio ser adquirido. Assim, as saรญdas sรฃo exibidas uma de cada vez e linha por linha.

Alรฉm dos bloqueios, o python tambรฉm oferece suporte a alguns outros mecanismos para lidar com a sincronizaรงรฃo de threads, conforme listado abaixo:

  1. RLocks
  2. Semaphores
  3. Condiรงรตes
  4. Eventos, e
  5. Barreiras

Bloqueio global de intรฉrprete (e como lidar com isso)

Antes de entrar nos detalhes do GIL do python, vamos definir alguns termos que serรฃo รบteis para a compreensรฃo da prรณxima seรงรฃo:

  1. Cรณdigo vinculado ร  CPU: refere-se a qualquer trecho de cรณdigo que serรก executado diretamente pela CPU.
  2. Cรณdigo vinculado a E/S: pode ser qualquer cรณdigo que acesse o sistema de arquivos atravรฉs do sistema operacional
  3. 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) em python รฉ um bloqueio de processo ou mutex usado ao lidar com os processos. Garante que um thread possa acessar um recurso especรญfico por vez e tambรฉm evita o uso de objetos e bytecodes de uma sรณ vez. Isso beneficia os programas de thread รบnico em um aumento de desempenho. GIL em python รฉ muito simples e fรกcil de implementar.

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 รฉ que ele usa um bloqueio global em cada processo do interpretador, o que significa que cada processo trata o prรณprio interpretador Python como um recurso.

Por exemplo, suponha que vocรช tenha escrito um programa python que usa dois threads para executar operaรงรตes de CPU e de 'E/S'. Quando vocรช executa este programa, acontece o seguinte:

  1. O interpretador python cria um novo processo e gera os threads
  2. Quando o thread-1 comeรงar a ser executado, ele primeiro adquirirรก o GIL e o bloquearรก.
  3. Se o thread-2 quiser executar agora, ele terรก que esperar a liberaรงรฃo do GIL, mesmo que outro processador esteja livre.
  4. 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.
  5. 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.

Devido a isso, apenas um thread pode acessar o interpretador a qualquer momento, o que significa que haverรก apenas um thread executando cรณdigo python em um determinado momento.

Isso รฉ bom em um processador de nรบcleo รบnico porque ele usaria divisรฃo de tempo (veja a primeira seรงรฃo deste tutorial) para lidar com os threads. No entanto, no caso de processadores multi-core, uma funรงรฃo vinculada ร  CPU executada em vรกrios threads terรก um impacto considerรกvel na eficiรชncia do programa, uma vez que na verdade nรฃo usarรก todos os nรบcleos disponรญveis ao mesmo tempo.

Por que o GIL foi necessรกrio?

O CPython garbage collector usa uma tรฉcnica eficiente de gerenciamento de memรณria conhecida como contagem de referรชncia. Veja como funciona: Cada objeto em python tem uma contagem de referรชncia, que รฉ aumentada quando รฉ atribuรญda a um novo nome de variรกvel ou adicionada a um contรชiner (como tuplas, listas, etc.). Da mesma forma, a contagem de referรชncia รฉ diminuรญda quando a referรชncia sai do escopo ou quando a instruรงรฃo del รฉ chamada. Quando a contagem de referรชncia de um objeto atinge 0, ele รฉ coletado como lixo e a memรณria alocada รฉ liberada.

Mas o problema รฉ que a variรกvel de contagem de referรชncia estรก sujeita a condiรงรตes de corrida como qualquer outra variรกvel global. Para resolver este problema, os desenvolvedores do python decidiram usar o bloqueio global do interpretador. A outra opรงรฃo era adicionar um bloqueio a cada objeto, o que resultaria em conflitos e aumentaria a sobrecarga das chamadas adquirir() e liberar().

Portanto, GIL รฉ uma restriรงรฃo significativa para programas python multithread que executam operaรงรตes pesadas vinculadas ร  CPU (tornando-os efetivamente de thread รบnico). Se vocรช quiser usar vรกrios nรบcleos de CPU em seu aplicativo, use o multiprocessamento mรณdulo em vez disso.

Resumo

  • Python suporta 2 mรณdulos para multithreading:
    1. __fio mรณdulo: fornece uma implementaรงรฃo de baixo nรญvel para threading e รฉ obsoleto.
    2. mรณdulo de threading: fornece uma implementaรงรฃo de alto nรญvel para multithreading e รฉ o padrรฃo atual.
  • Para criar um thread usando o mรณdulo threading, vocรช deve fazer o seguinte:
    1. Crie uma classe que estenda o Fio classe.
    2. Substitua seu construtor (__init__).
    3. Substituir seu corre() mรฉtodo.
    4. Crie um objeto desta classe.
  • Um thread pode ser executado chamando o mรฉtodo comeรงar() mรฉtodo.
  • O processo de Junte-se() O mรฉtodo pode ser usado para bloquear outros threads atรฉ que este thread (aquele no qual o join foi chamado) termine a execuรงรฃo.
  • Uma condiรงรฃo de corrida ocorre quando vรกrios threads acessam ou modificam um recurso compartilhado ao mesmo tempo.
  • Pode ser evitado por Synccronizando os fios.
  • Python suporta 6 maneiras de sincronizar threads:
    1. Locks
    2. RLocks
    3. Semaphores
    4. Condiรงรตes
    5. Eventos, e
    6. Barreiras
  • Os bloqueios permitem que apenas um thread especรญfico que adquiriu o bloqueio entre na seรงรฃo crรญtica.
  • Um bloqueio possui 2 mรฉtodos principais:
    1. adquirir(): Define o estado de bloqueio para trancado. Se chamado em um objeto bloqueado, ele serรก bloqueado atรฉ que o recurso seja liberado.
    2. liberar(): Define o estado de bloqueio para desbloqueado e retorna. Se chamado em um objeto desbloqueado, ele retorna falso.
  • O bloqueio global do interpretador รฉ um mecanismo atravรฉs do qual apenas 1 CPython o processo do interpretador pode ser executado por vez.
  • Foi usado para facilitar a funcionalidade de contagem de referรชncia do CPythoncoletor de lixo de s.
  • Para fazer Python Para aplicativos com operaรงรตes pesadas que exigem muita CPU, vocรช deve usar o mรณdulo de multiprocessamento.

Resuma esta postagem com: