Gostaria de contribuir com um exemplo simples e as explicações que achei úteis quando tive que resolver esse problema pessoalmente.
Nesta resposta, você encontrará algumas informações sobre o GIL (bloqueio global de intérpretes) do Python e um exemplo simples do dia-a-dia escrito usando multiprocessing.dummy, além de alguns benchmarks simples.
Bloqueio global de intérpretes (GIL)
Python não permite multi-threading no sentido mais verdadeiro da palavra. Ele tem um pacote multiencadeado, mas se você deseja multiencadear para acelerar seu código, geralmente não é uma boa ideia usá-lo.
O Python possui uma construção chamada bloqueio global de intérpretes (GIL). O GIL garante que apenas um dos seus 'threads' possa ser executado a qualquer momento. Um segmento adquire o GIL, faz um pouco de trabalho e passa o GIL para o próximo segmento.
Isso acontece muito rapidamente e, para o olho humano, pode parecer que seus threads estão executando paralelamente, mas na verdade eles estão apenas se revezando usando o mesmo núcleo da CPU.
Toda essa passagem do GIL adiciona sobrecarga à execução. Isso significa que, se você deseja que seu código seja executado mais rapidamente, o uso do pacote threading geralmente não é uma boa ideia.
Existem razões para usar o pacote de encadeamento do Python. Se você deseja executar algumas coisas simultaneamente, e a eficiência não é uma preocupação, é totalmente adequado e conveniente. Ou se você estiver executando um código que precisa esperar por algo (como algumas E / S), isso pode fazer muito sentido. Mas a biblioteca de threads não permitirá que você use núcleos extras da CPU.
A multithreading pode ser terceirizada para o sistema operacional (executando o multiprocessamento) e algum aplicativo externo que chama seu código Python (por exemplo, Spark ou Hadoop ) ou algum código que seu código Python chama (por exemplo: você pode faça com que seu código Python chame uma função C que faça o material multiencadeado caro).
Por que isso importa
Porque muitas pessoas passam muito tempo tentando encontrar gargalos em seu código multiencadeado Python sofisticado antes de aprenderem o que é o GIL.
Depois que essas informações estiverem claras, eis o meu código:
#!/bin/python
from multiprocessing.dummy import Pool
from subprocess import PIPE,Popen
import time
import os
# In the variable pool_size we define the "parallelness".
# For CPU-bound tasks, it doesn't make sense to create more Pool processes
# than you have cores to run them on.
#
# On the other hand, if you are using I/O-bound tasks, it may make sense
# to create a quite a few more Pool processes than cores, since the processes
# will probably spend most their time blocked (waiting for I/O to complete).
pool_size = 8
def do_ping(ip):
if os.name == 'nt':
print ("Using Windows Ping to " + ip)
proc = Popen(['ping', ip], stdout=PIPE)
return proc.communicate()[0]
else:
print ("Using Linux / Unix Ping to " + ip)
proc = Popen(['ping', ip, '-c', '4'], stdout=PIPE)
return proc.communicate()[0]
os.system('cls' if os.name=='nt' else 'clear')
print ("Running using threads\n")
start_time = time.time()
pool = Pool(pool_size)
website_names = ["www.google.com","www.facebook.com","www.pinterest.com","www.microsoft.com"]
result = {}
for website_name in website_names:
result[website_name] = pool.apply_async(do_ping, args=(website_name,))
pool.close()
pool.join()
print ("\n--- Execution took {} seconds ---".format((time.time() - start_time)))
# Now we do the same without threading, just to compare time
print ("\nRunning NOT using threads\n")
start_time = time.time()
for website_name in website_names:
do_ping(website_name)
print ("\n--- Execution took {} seconds ---".format((time.time() - start_time)))
# Here's one way to print the final output from the threads
output = {}
for key, value in result.items():
output[key] = value.get()
print ("\nOutput aggregated in a Dictionary:")
print (output)
print ("\n")
print ("\nPretty printed output: ")
for key, value in output.items():
print (key + "\n")
print (value)