Este artigo cobre os fundamentos de multithreading na linguagem de programação Python. Assim como o multiprocessamento , o multithreading é uma forma de realizar multitarefa. Em multithreading, o conceito de threads é usado.

Vamos primeiro entender o conceito de thread na arquitetura de computador.

Fio

Na computação, um processo é uma instância de um programa de computador que está sendo executado. Qualquer processo tem 3 componentes básicos:

  • Um programa executável.
  • Os dados associados necessários para o programa (variáveis, espaço de trabalho, buffers, etc.)
  • O contexto de execução do programa (estado do processo)

Um thread é uma entidade dentro de um processo que pode ser agendada para execução. Além disso, é a menor unidade de processamento que pode ser executada em um SO (Sistema Operacional).



Em palavras simples, um thread é uma sequência de tais instruções dentro de um programa que pode ser executado independentemente de outro código. Para simplificar, você pode assumir que um thread é simplesmente um subconjunto de um processo!

Um thread contém todas essas informações em um Thread Control Block (TCB) :

  • Identificador de thread: ID exclusivo (TID) é atribuído a cada novo thread
  • Ponteiro de pilha: aponta para a pilha do thread no processo. Stack contém as variáveis ​​locais no escopo do thread.
  • Contador de programa: um registro que armazena o endereço da instrução atualmente em execução por thread.
  • Estado do thread: pode estar em execução, pronto, esperando, iniciar ou concluído.
  • Conjunto de registros do thread: registros atribuídos ao thread para cálculos.
  • Ponteiro de processo pai: Um ponteiro para o bloco de controle de processo (PCB) do processo em que o thread reside.

Considere o diagrama abaixo para entender a relação entre o processo e seu encadeamento:

Multithreading

Vários threads podem existir dentro de um processo onde:

  • Cada thread contém seu próprio conjunto de registros e variáveis ​​locais (armazenadas na pilha) .
  • Todas as threads de um processo compartilham variáveis ​​globais (armazenadas em heap) e o código do programa .

Considere o diagrama abaixo para entender como vários threads existem na memória:



Multithreading é definido como a capacidade de um processador de executar vários threads simultaneamente.

Em uma CPU simples de núcleo único, isso é obtido usando a alternância frequente entre threads. Isso é denominado como troca de contexto . Na troca de contexto, o estado de um thread é salvo e o estado de outro thread é carregado sempre que ocorre qualquer interrupção (devido a I / O ou manualmente). A troca de contexto ocorre com tanta frequência que todos os threads parecem estar executando paralelamente (isso é denominado como multitarefa ).

Considere o diagrama abaixo, no qual um processo contém dois threads ativos:

Multithreading em Python

Em Python, o módulo de threading fornece uma API muito simples e intuitiva para gerar vários threads em um programa.

Vamos considerar um exemplo simples usando o módulo de threading:

import threading 
  
def print_cube(num): 
    
    
    
    print("Cube: {}".format(num * num * num)) 
  
def print_square(num): 
    
    
    
    print("Square: {}".format(num * num)) 
  
if __name__ == "__main__": 
    
    t1 = threading.Thread(target=print_square, args=(10,)) 
    t2 = threading.Thread(target=print_cube, args=(10,)) 
  
    
    t1.start() 
    
    t2.start() 
  
    
    t1.join() 
    
    t2.join() 
  
    
    print("Done!") 
Quadrado: 100
Cubo: 1000
Feito!

Vamos tentar entender o código acima:

  • Para importar o módulo de threading, fazemos:
    importar threading
  • Para criar um novo thread, criamos um objeto da classe Thread . Leva os seguintes argumentos:
    • alvo : a função a ser executada por thread
    • args : os argumentos a serem passados ​​para a função de destino

    No exemplo acima, criamos 2 threads com funções de destino diferentes:

    t1 = threading.Thread (target = print_square, args = (10,))
    t2 = threading.Thread (target = print_cube, args = (10,))
  • Para iniciar um thread, usamos o método start de Thread classe .
    t1.start()
    t2.start()
  • Depois que os threads são iniciados, o programa atual (você pode pensar nele como um thread principal) também continua em execução. A fim de parar a execução do programa atual até que um thread seja concluído, usamos join método de .
    t1.join()
    t2.join()

    Como resultado, o programa atual primeiro aguardará a conclusão de t1 e depois t2 . Depois de concluídas, as instruções restantes do programa atual são executadas.

Considere o diagrama abaixo para uma melhor compreensão de como funciona o programa acima:



Considere o programa python fornecido a seguir, no qual imprimimos o nome do thread e o processo correspondente para cada tarefa:

import threading 
import os 
  
def task1(): 
    print("Task 1 assigned to thread: {}".format(threading.current_thread().name)) 
    print("ID of process running task 1: {}".format(os.getpid())) 
  
def task2(): 
    print("Task 2 assigned to thread: {}".format(threading.current_thread().name)) 
    print("ID of process running task 2: {}".format(os.getpid())) 
  
if __name__ == "__main__": 
  
    
    print("ID of process running main program: {}".format(os.getpid())) 
  
    
    print("Main thread name: {}".format(threading.current_thread().name)) 
  
    
    t1 = threading.Thread(target=task1, name='t1') 
    t2 = threading.Thread(target=task2, name='t2')   
  
    
    t1.start() 
    t2.start() 
  
    
    t1.join() 
    t2.join() 
ID do processo executando o programa principal: 11758
Nome do tópico principal: MainThread
Tarefa 1 atribuída ao tópico: t1
ID do processo em execução na tarefa 1: 11758
Tarefa 2 atribuída ao tópico: t2
ID do processo em execução na tarefa 2: 11758

Vamos tentar entender o código acima:

  • Usamos a função os.getpid() para obter a ID do processo atual.
    print ("ID do processo executando o programa principal: {}". format (os.getpid()))

    Como fica claro na saída, o ID do processo permanece o mesmo para todos os threads.

  • Usamos a função threading.main_thread() para obter o objeto thread principal. Em condições normais, a thread principal é a thread a partir da qual o interpretador Python foi iniciado. O atributo name do objeto thread é usado para obter o nome do thread.
    print ("Nome do thread principal: {}". format (threading.main_thread(). name))
  • Usamos a função threading.current_thread() para obter o objeto thread atual.
    print ("Tarefa 1 atribuída ao tópico: {}". format (threading.current_thread(). nome))

O diagrama abaixo esclarece o conceito acima:

Portanto, esta foi uma breve introdução ao multithreading em Python. O próximo artigo desta série cobre a sincronização entre vários threads .

Multithreading em Python | Conjunto 2 (sincronização)

Este artigo foi contribuído por Nikhil Kumar . Se você gosta de GeeksforGeeks e gostaria de contribuir, você também pode escrever um artigo usando contribute.geeksforgeeks.org ou enviar o seu artigo para contribute@geeksforgeeks.org. Veja o seu artigo que aparece na página principal do GeeksforGeeks e ajude outros Geeks.

Escreva comentários se encontrar algo incorreto ou se quiser compartilhar mais informações sobre o tópico discutido acima.