Python Threading: Uma Introdução

Neste tutorial, você aprenderá como usar o built-in do Python rosqueamento módulo para explorar recursos multithreading em Python.

Começando com os conceitos básicos de processos e threads, você aprenderá como o multithreading funciona em Python — ao mesmo tempo em que compreende os conceitos de simultaneidade e paralelismo. Você aprenderá como iniciar e executar um ou mais threads em Python usando o built-in threading módulo.

Vamos começar.

Processos vs. Threads: Diferenças

O que é um processo?

A processo é qualquer instância de um programa que precisa ser executado.

Pode ser qualquer coisa – um script Python ou um navegador da web como Chrome a um aplicativo de videoconferência. Se você lançar o Gerenciador de tarefas em sua máquina e navegue até Desempenho –> CPU você poderá ver os processos e threads que estão em execução nos núcleos da CPU.

Entendendo Processos e Threads

Internamente, um processo possui uma memória dedicada que armazena o código e os dados correspondentes ao processo.

Um processo consiste em um ou mais tópicos. Um thread é a menor sequência de instruções que o sistema operacional pode executar e representa o fluxo de execução.

Cada thread tem sua própria pilha e registradores, mas não uma memória dedicada. Todos os threads associados a um processo podem acessar os dados. Portanto, dados e memória são compartilhados por todos os threads de um processo.

processo-e-threads

Em uma CPU com N núcleos, N processos podem ser executados em paralelo no mesmo instante de tempo. No entanto, dois threads do mesmo processo nunca podem ser executados em paralelo, mas podem ser executados simultaneamente. Abordaremos o conceito de simultaneidade versus paralelismo na próxima seção.

Com base no que aprendemos até agora, vamos resumir as diferenças entre um processo e um thread.

RecursoProcessoFio
MemóriaMemória dedicadaMemoria compartilhada
Modo de execuçãoParalelo, concorrenteSimultâneo; mas não paralelo
Execução tratada por Sistema operacionalCPython InterpreterName

Multithread em Python

Em Python, o Bloqueio de intérprete global (GIL) Assegura que apenas um thread pode adquirir o bloqueio e executar a qualquer momento. Todos os threads devem adquirir esse bloqueio para serem executados. Isso garante que apenas um único thread possa estar em execução – em qualquer ponto no tempo – e evita simultâneo multithreading.

Por exemplo, considere duas threads, t1 e t2, do mesmo processo. Como as threads compartilham os mesmos dados, quando t1 está lendo um valor específico k, t2 pode modificar o mesmo valor k. Isso pode levar a impasses e resultados indesejáveis. Mas apenas um dos threads pode adquirir o bloqueio e executar a qualquer momento. Assim, a GIL garante também segurança do fio.

Então, como alcançamos recursos de multithreading em Python? Para entender isso, vamos discutir os conceitos de simultaneidade e paralelismo.

Simultaneidade x paralelismo: uma visão geral

Considere uma CPU com mais de um núcleo. Na ilustração abaixo, a CPU tem quatro núcleos. Isso significa que podemos ter quatro operações diferentes rodando em paralelo a qualquer instante.

Se houver quatro processos, cada um deles poderá ser executado de forma independente e simultânea em cada um dos quatro núcleos. Vamos supor que cada processo tenha dois threads.

paralelismo multicore

Para entender como o threading funciona, vamos mudar da arquitetura de processador multicore para single-core. Conforme mencionado, apenas um único thread pode estar ativo em uma determinada instância de execução; mas o núcleo do processador pode alternar entre os threads.

código

Por exemplo, os encadeamentos vinculados a E/S geralmente esperam por operações de E/S: leitura na entrada do usuário, leituras de banco de dados e operações de arquivo. Durante esse tempo de espera, o encadeamento limitado por E/S pode liberar o bloqueio para que o outro thread possa ser executado. O tempo de espera também pode ser uma operação simples, como dormir por n segundos.

Resumindo: Durante as operações de espera, o thread libera o bloqueio, permitindo que o núcleo do processador mude para outro thread. O thread anterior retoma a execução após o término do período de espera. Esse processo, no qual o núcleo do processador alterna entre os threads simultaneamente, facilita o multithreading. ✅

Se você deseja implementar paralelismo em nível de processo em seu aplicativo, considere usar multiprocessamento em vez de.

Python Threading Module: primeiros passos

O Python vem com um threading módulo que você pode importar para o script Python.

  import threading

Para criar um objeto thread em Python, você pode usar o Thread construtor: threading.Thread(...). Esta é a sintaxe genérica que é suficiente para a maioria das implementações de threading:

  threading.Thread(target=...,args=...)

Aqui,

  • target é o argumento de palavra-chave que denota um Python callable
  • args é a tupla de argumentos que o destino recebe.

Você precisará do Python 3.x para executar os exemplos de código neste tutorial. Baixe o código e acompanhe.

Definir e executar threads em Python

Vamos definir um thread que executa uma função de destino.

A função alvo é some_func.

  import threading
import time

def some_func():
    print("Running some_func...")
    time.sleep(2)
    print("Finished running some_func.")

thread1 = threading.Thread(target=some_func)
thread1.start()
print(threading.active_count())

Vamos analisar o que o trecho de código acima faz:

  • Ele importa o threading e a time módulos.
  • A função some_func tem descritivo print() declarações e inclui uma operação de suspensão por dois segundos: time.sleep(n) faz com que a função durma por n segundos.
  • Em seguida, definimos um thread thread_1 com o alvo como some_func. threading.Thread(target=...) cria um objeto de encadeamento.
  • Observação : Especifique o nome da função e não uma chamada de função; usar some_func e não some_func().
  • Criando um objeto de thread não iniciar um segmento; chamando o start() método no objeto thread faz.
  • Para obter o número de threads ativos, usamos o método active_count() função.

O script Python está sendo executado no thread principal e estamos criando outro thread (thread1) para executar a função some_func; portanto, a contagem de encadeamentos ativos é dois, conforme visto na saída:

  # Output
Running some_func...
2
Finished running some_func.

Se olharmos mais de perto a saída, veremos que ao iniciar thread1, a primeira instrução de impressão é executada. Mas durante a operação de hibernação, o processador muda para o thread principal e imprime o número de threads ativos – sem esperar thread1 para terminar de executar.

thread1-ex

Esperando que os Threads terminem a execução

Se você quiser thread1 para terminar a execução, você pode chamar o join() nele após iniciar o thread. Fazendo isso vai esperar thread1 para terminar a execução sem mudar para o thread principal.

  import threading
import time

def some_func():
    print("Running some_func...")
    time.sleep(2)
    print("Finished running some_func.")

thread1 = threading.Thread(target=some_func)
thread1.start()
thread1.join()
print(threading.active_count())

Agora, thread1 terminou a execução antes de imprimirmos a contagem de threads ativa. Portanto, apenas o thread principal está em execução, o que significa que a contagem de threads ativos é um. ✅

  # Output
Running some_func...
Finished running some_func.
1

Como executar vários threads em Python

Em seguida, vamos criar dois threads para executar duas funções diferentes.

Aqui, count_down é uma função que recebe um número como argumento e faz a contagem regressiva desse número até zero.

  def count_down(n):
    for i in range(n,-1,-1):
        print(i)

nós definimos count_upoutra função do Python que conta de zero até um determinado número.

  def count_up(n):
    for i in range(n+1):
        print(i)

Ao usar o range() função com a sintaxe range(start, stop, step)o ponto final stop é excluído por padrão.

– Para fazer uma contagem regressiva de um número específico até zero, você pode usar um negativo step valor de -1 e defina o stop valor para -1 para que zero seja incluído.

– Da mesma forma, para contar até nvocê deve definir o stop valor para n + 1. Como os valores padrão de start e step são 0 e 1, respectivamente, você pode usar range(n + 1) para obter a sequência de 0 a n.

Em seguida, definimos duas threads, thread1 e thread2 para executar as funções count_down e count_up, respectivamente. Nós adicionamos print declarações e sleep operações para ambas as funções.

Ao criar os objetos de encadeamento, observe que os argumentos para a função de destino devem ser especificados como uma tupla – para o args parâmetro. Como ambas as funções (count_down e count_up) aceita um argumento, você terá que inserir uma vírgula explicitamente após o valor. Isso garante que o argumento ainda seja passado como uma tupla, pois os elementos subsequentes são inferidos como None.

  import threading
import time

def count_down(n):
    for i in range(n,-1,-1):
        print("Running thread1....")
        print(i)
        time.sleep(1)


def count_up(n):
    for i in range(n+1):
        print("Running thread2...")
        print(i)
        time.sleep(1)

thread1 = threading.Thread(target=count_down,args=(10,))
thread2 = threading.Thread(target=count_up,args=(5,))
thread1.start()
thread2.start()

Na saída:

  • A função count_up corre em thread2 e conta até 5 começando em 0.
  • O count_down função é executada em thread1 contagem regressiva de 10 a 0.
  # Output
Running thread1....
10
Running thread2...
0
Running thread1....
9
Running thread2...
1
Running thread1....
8
Running thread2...
2
Running thread1....
7
Running thread2...
3
Running thread1....
6
Running thread2...
4
Running thread1....
5
Running thread2...
5
Running thread1....
4
Running thread1....
3
Running thread1....
2
Running thread1....
1
Running thread1....
0

Você pode ver isso thread1 e thread2 execute alternadamente, pois ambos envolvem uma operação de espera (dormir). Uma vez o count_up a função terminou de contar até 5, thread2 não está mais ativo. Assim, obtemos a saída correspondente a apenas thread1.

Resumindo

Neste tutorial, você aprendeu como usar o módulo de encadeamento interno do Python para implementar o multithreading. Aqui está um resumo das principais conclusões:

  • O Fio O construtor pode ser usado para criar um objeto de encadeamento. Usando threading.Thread(target=,args=()) cria uma thread que executa o alvo chamável com argumentos especificados em argumentos.
  • O programa Python é executado em um thread principal, portanto, os objetos de thread que você cria são threads adicionais. Você pode ligar contagem_ativa() A função retorna o número de threads ativos em qualquer instância.
  • Você pode iniciar um tópico usando o começar() método no objeto thread e espere até que ele termine a execução usando o método juntar() método.

Você pode exemplos adicionais de código ajustando os tempos de espera, tentando uma operação de E/S diferente e muito mais. Certifique-se de implementar multithreading em seu próximo Projetos Python . Feliz codificação!

Artigos relacionados