Multithreading e Multiprocessing são as duas formas mais comuns de obter simultaneidade e paralelização, no entanto, poucos desenvolvedores entendem a diferença entre eles e falham em escolher efetivamente qual usar e quando.
Neste artigo, discutiremos as diferenças entre multithreading e multiprocessamento e como decidir o que usar e como implementá-lo em Python.
Um thread é um fluxo independente de execução. Pode ser visto essencialmente como um componente individual leve de um processo, que pode ser executado paralelamente. Threading é um recurso geralmente fornecido pelo sistema operacional. Pode haver vários threads em um processo, que compartilham o mesmo espaço de memória, o que significa que eles compartilham o código a ser executado e as variáveis declaradas no programa entre si.
Para entender isso melhor, vamos considerar um exemplo dos programas em execução em seu laptop agora. Você provavelmente está lendo este artigo com várias abas abertas em seu navegador. Enquanto isso, você tem o aplicativo de desktop Spotify aberto para ouvir música. Agora, o navegador e o aplicativo de desktop Spotify são como dois processos distintos que podem empregar vários processos ou threads para alcançar o paralelismo. Portanto, as diferentes guias do seu navegador podem ser executadas em diferentes threads. Da mesma forma, o Spotify pode reproduzir música usando um thread e usar outro para baixar sua música favorita da Internet e usar um terceiro para exibir a interface do usuário. E isso é chamado Multithreading.
Multithreading, como o nome sugere, é uma tarefa ou operação que pode executar vários threads ao mesmo tempo. É uma técnica popular que agiliza várias tarefas em rápida sucessão ao mesmo tempo e facilita o compartilhamento rápido e fácil de recursos entre vários encadeamentos com o encadeamento principal.
A imagem a seguir explica o Multithreading em Python:
Python é uma linguagem linear, mas podemos usar o módulo Threading Python para entender e implementar o conceito de Multithreading em Python. O módulo de encadeamento oferece uma API intuitiva para gerar facilmente vários encadeamentos que podem ser usados quando houver necessidade de mais poder de processamento.
Pode ser usado como mostrado abaixo:
import threading from queue import Queue import time def testThread(num): print num if __name__ == '__main__': for i in range(5): t = threading.Thread(target=testThread, arg=(i,)) t.start()
No trecho de código acima, target
é usado como o objeto que pode ser chamado, args
para passar parâmetros para a função e start
para iniciar o thread.
Agora, aqui vem algo interessante - a fechadura.
Muitas vezes, há casos na programação em que você deseja que seus encadeamentos possam modificar ou usar as variáveis comuns aos encadeamentos. No entanto, para fazer isso, você terá que usar algo conhecido como Lock ou Global Interpreter Lock (GIL) em Python.
do Python
No CPython, o global interpreter lock , ou GIL , é um mutex que protege o acesso a objetos Python, impedindo que vários threads executem bytecodes Python de uma só vez. Esse bloqueio é necessário principalmente porque o gerenciamento de memória do CPython não é thread-safe.
No nível do interpretador, o Python basicamente serializa as instruções. Para que qualquer thread execute qualquer função, ela deve primeiro obter um bloqueio global. Como apenas um thread pode obter esse bloqueio por vez, o interpretador deve finalmente executar as instruções em série. Essa arquitetura torna o gerenciamento de memória thread-safe, mas não pode usar vários núcleos de CPU.
Simplificando, sempre que uma função deseja usar ou modificar uma variável, ela bloqueia essa variável de forma que, se qualquer outra função quiser usar ou modificar essa variável específica, ela terá que esperar até que essa variável seja desbloqueada.
Considere duas funções que iteram uma variável por um. Você pode usar o bloqueio para garantir que uma função possa ler a variável, executar cálculos e escrever de volta antes que outra função possa, para que possamos evitar corrupção de dados.
Threading em Python é mais útil para operações de E/S ou tarefas vinculadas à rede, como a execução de scripts, por exemplo, no caso de web scraping, em vez de tarefas que podem exigir muito da CPU. Outro exemplo é o Tensorflow , que usa um pool de threads para transformar dados em paralelo.
Além desses aplicativos, as Interfaces Gráficas do Usuário (GUIs) usam Multithreading o tempo todo para tornar os aplicativos responsivos e interativos. Um exemplo comum pode ser um programa de edição de texto onde, assim que o usuário insere o texto, ele é exibido na tela. Aqui, uma thread cuida da entrada do usuário enquanto a outra lida com a tarefa de exibi-la. Podemos adicionar mais tópicos para mais funcionalidades, como verificação ortográfica, preenchimento automático e assim por diante.
Agora, tendo discutido os threads em detalhes, vamos passar para os processos.
Um processo é simplesmente uma instância do programa de computador que está sendo executado. Cada processo tem seu próprio espaço de memória que é usado para armazenar as instruções que estão sendo executadas e quaisquer dados que precise acessar ou armazenar para a execução do código. Por causa disso, gerar um processo é mais demorado e lento em comparação com um thread.
Como discutimos anteriormente, quando estamos executando vários aplicativos em nosso desktop, cada aplicativo é um processo e quando estamos executando esses processos ao mesmo tempo, é chamado de Multiprocessamento.
Multiprocessamento é a capacidade de um processador executar várias tarefas não relacionadas simultaneamente. Ele permite que você crie programas que podem ser executados simultaneamente, ignorando o Global Interpreter Lock (GIL) e usando todo o núcleo da CPU para execução eficiente de tarefas.
Embora o conceito de multiprocessamento seja fundamentalmente diferente do multithreading, sua sintaxe ou uso em Python é bastante semelhante. Semelhante ao módulo Threading, temos um módulo Multiprocessing em Python que auxilia na geração de diferentes processos, onde cada processo possui seu próprio interpretador Python e um GIL.
Como os processos não compartilham a mesma memória, eles não podem modificar a mesma memória simultaneamente, evitando o risco de entrar em um impasse ou chances de corrupção de dados.
Pode ser usado como mostrado abaixo:
import multiprocessing def spawn(num): print(num) if __name__ == '__main__': for i in range(5): p = multiprocessing.Process(target=spawn, args=(i,)) p.start() p.join() # this line allows you to wait for processes
Como discutimos anteriormente, o multiprocessamento é uma escolha mais sábia caso as tarefas sejam extensas da CPU e não tenham nenhuma operação de E/S ou interações do usuário.
Aqui estão alguns pontos para resumir as diferenças, méritos e desvantagens do Multiprocessamento e Multithreading:
Podemos tirar as seguintes conclusões dessa discussão:
Agora que você entende como o multiprocessamento e o multithreading do Python operam e como eles se comparam, você pode escrever código de forma eficaz e aplicar as duas abordagens em várias circunstâncias.
Espero que você tenha achado este artigo útil. Continue lendo!