Threading Python: Uma Introdução – etechpt.com

Neste tutorial, você aprenderá a usar o módulo de encadeamento integrado do Python para explorar os recursos de multiencadeamento no Python.

Começando com o básico de processos e threads, você aprenderá como o multithreading funciona em Python — enquanto entende os conceitos de simultaneidade e paralelismo. Você aprenderá então como iniciar e executar um ou mais threads em Python usando o módulo de threading integrado.

Vamos começar.

Processos vs. Threads: Quais são as diferenças?

O que é um processo?

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

Pode ser qualquer coisa – um script Python ou um navegador da Web, como o Chrome, para um aplicativo de videoconferência. Se você iniciar o Gerenciador de Tarefas em sua máquina e navegar até Desempenho –> CPU, poderá ver os processos e threads que estão sendo executados atualmente em seus núcleos de 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 threads. Uma 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 todas as threads de um processo.

Em uma CPU com N núcleos, N processos podem ser executados em paralelo na mesma instância de tempo. No entanto, duas threads do mesmo processo nunca podem ser executadas em paralelo, mas podem ser executadas 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.

FeatureProcessThreadMemoryMemória dedicadaMemória compartilhadaModo de execuçãoParalelo, concurrentConcurrent; mas não parallelExecution manipulado pelo Operating SystemCPython Interpreter

Multithreading em Python

Em Python, o Global Interpreter Lock (GIL) garante que apenas um thread possa adquirir o bloqueio e ser executado a qualquer momento. Todos os threads devem adquirir esse bloqueio para serem executados. Isso garante que apenas um único encadeamento possa estar em execução – em um determinado momento – e evita multiencadeamento simultâneo.

  Como alterar sua foto de perfil no Strava

Por exemplo, considere duas threads, t1 e t2, do mesmo processo. Como as threads compartilham os mesmos dados quando t1 está lendo um determinado valor 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 em qualquer instância. Portanto, o GIL também garante a segurança da rosca.

Então, como alcançamos recursos 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 sendo executadas em paralelo em 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 duas threads.

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 instância de execução específica; mas o núcleo do processador pode alternar entre os threads.

Por exemplo, encadeamentos vinculados a E/S geralmente aguardam 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, ele 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.

Em resumo: Durante as operações de espera, o encadeamento libera o bloqueio, permitindo que o núcleo do processador alterne para outro encadeamento. O thread anterior retoma a execução após a conclusão do período de espera. Esse processo, em que o núcleo do processador alterna entre os threads simultaneamente, facilita o multithreading. ✅

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

Módulo de Threading Python: Primeiros Passos

O Python é fornecido com um módulo de encadeamento que você pode importar para o script Python.

import threading

Para criar um objeto thread em Python, você pode usar o construtor Thread: 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.
  Como pagar a gasolina sem sair do carro

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

Como definir e executar threads em Python

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

A função de destino é 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:

  • Importa os módulos de threading e time.
  • A função some_func tem instruções print() descritivas 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 destino como some_func. threading.Thread(target=…) cria um objeto thread.
  • Nota: Especifique o nome da função e não uma chamada de função; use some_func e não some_func().
  • A criação de um objeto de encadeamento não inicia um encadeamento; chamar o método start() no objeto thread faz.
  • Para obter o número de threads ativos, usamos a função active_count().

O script Python está sendo executado no encadeamento principal e estamos criando outro encadeamento (thread1) para executar a função some_func para que a contagem de encadeamentos ativos seja 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 o thread1, a primeira instrução de impressão é executada. Mas durante a operação de suspensão, o processador alterna para o thread principal e imprime o número de threads ativos – sem esperar que o thread1 termine a execução.

Aguardando threads para concluir a execução

Se você quiser que o thread1 termine a execução, você pode chamar o método join() nele depois de iniciar o thread. Fazê-lo vai esperar que o thread1 termine a execução sem alternar 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 encadeamento principal está em execução, o que significa que a contagem de encadeamentos 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)

Definimos count_up, outra função Python que conta de zero até um determinado número.

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

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

  Como configurar e usar o túnel SSH

– Para fazer a contagem regressiva de um número específico até zero, você pode usar um valor de passo negativo de -1 e definir o valor de parada como -1 para que zero seja incluído.

– Da mesma forma, para contar até n, você deve definir o valor de parada 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 0 através de n.

Em seguida, definimos dois threads, thread1 e thread2 para executar as funções count_down e count_up, respectivamente. Adicionamos instruções de impressão e operações de suspensão para ambas as funções.

Ao criar os objetos de thread, observe que os argumentos para a função de destino devem ser especificados como uma tupla — para o parâmetro args. Como ambas as funções (count_down e count_up) recebem 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 Nenhum.

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 é executada em thread2 e conta até 5 começando em 0.
  • A função count_down é executada na contagem regressiva do thread1 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 que thread1 e thread2 executam alternadamente, pois ambos envolvem uma operação de espera (sleep). Depois que a função count_up terminar de contar até 5, o thread2 não estará mais ativo. Assim, obtemos a saída correspondente apenas a thread1.

Resumindo

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

  • O construtor Thread pode ser usado para criar um objeto de thread. Usando threading.Thread(target=,args=()) cria um thread que executa o destino callable com argumentos especificados em args.
  • O programa Python é executado em um encadeamento principal, portanto, os objetos de encadeamento que você cria são encadeamentos adicionais. Você pode chamar a função active_count() retorna o número de threads ativos em qualquer instância.
  • Você pode iniciar um thread usando o método start() no objeto thread e esperar até que ele termine a execução usando o método join().

Você pode codificar exemplos adicionais ajustando os tempos de espera, tentando uma operação de E/S diferente e muito mais. Certifique-se de implementar multithreading em seus próximos projetos Python. Boa codificação!🎉