Como posso usar a segmentação em Python?

1281

Estou tentando entender o encadeamento em Python. Examinei a documentação e os exemplos, mas, francamente, muitos exemplos são excessivamente sofisticados e estou tendo problemas para entendê-los.

Como você mostra claramente as tarefas sendo divididas para multiencadeamento?

albruno
fonte
31
Uma boa discussão geral sobre esse tópico pode ser encontrada no Problema Mais Difícil do Python, por Jeff Knupp. Em resumo, parece que o encadeamento não é para iniciantes.
Matthew Walker
112
haha, eu tendem a pensar que threading é para todos, mas os novatos não são para enfiar :)))))
Bohdan
42
Apenas para sinalizar que as pessoas devem ler todas as respostas como os posteriores são indiscutivelmente melhor como novos recursos de linguagem são aproveitadas ...
Gwyn Evans
5
Lembre-se de escrever sua lógica principal em C e chame-a por ctypes para realmente tirar proveito do encadeamento do Python.
Aaa90210
4
Eu só queria acrescentar que PyPubSub é uma ótima maneira de enviar e receber mensagens para controlar o fluxo da linha
ytpillai

Respostas:

1418

Desde que essa pergunta foi feita em 2010, houve uma simplificação real de como executar multithreading simples com Python com mapa e pool .

O código abaixo vem de uma publicação de artigo / blog que você definitivamente deveria conferir (sem afiliação) - Paralelismo em uma linha: um modelo melhor para as tarefas de segmentação do dia a dia . Resumirei abaixo - acaba sendo apenas algumas linhas de código:

from multiprocessing.dummy import Pool as ThreadPool
pool = ThreadPool(4)
results = pool.map(my_function, my_array)

Qual é a versão multithread de:

results = []
for item in my_array:
    results.append(my_function(item))

Descrição

O mapa é uma pequena função interessante e a chave para injetar facilmente paralelismo no seu código Python. Para quem não conhece, o mapa é algo retirado de linguagens funcionais como o Lisp. É uma função que mapeia outra função em uma sequência.

O Map lida com a iteração sobre a sequência, aplica a função e armazena todos os resultados em uma lista útil no final.

Digite a descrição da imagem aqui


Implementação

Versões paralelas da função map são fornecidas por duas bibliotecas: multiprocessamento e também seu filho filho pouco conhecido, mas igualmente fantástico: multiprocessing.dummy.

multiprocessing.dummyé exatamente o mesmo que o módulo de multiprocessamento, mas usa threads ( uma distinção importante - use vários processos para tarefas intensivas em CPU; threads para (e durante) E / S ):

multiprocessing.dummy replica a API do multiprocessamento, mas não passa de um invólucro em torno do módulo de segmentação.

import urllib2
from multiprocessing.dummy import Pool as ThreadPool

urls = [
  'http://www.python.org',
  'http://www.python.org/about/',
  'http://www.onlamp.com/pub/a/python/2003/04/17/metaclasses.html',
  'http://www.python.org/doc/',
  'http://www.python.org/download/',
  'http://www.python.org/getit/',
  'http://www.python.org/community/',
  'https://wiki.python.org/moin/',
]

# Make the Pool of workers
pool = ThreadPool(4)

# Open the URLs in their own threads
# and return the results
results = pool.map(urllib2.urlopen, urls)

# Close the pool and wait for the work to finish
pool.close()
pool.join()

E o tempo resulta:

Single thread:   14.4 seconds
       4 Pool:   3.1 seconds
       8 Pool:   1.4 seconds
      13 Pool:   1.3 seconds

Passando vários argumentos (funciona assim apenas no Python 3.3 e posterior ):

Para passar várias matrizes:

results = pool.starmap(function, zip(list_a, list_b))

Ou para passar uma constante e uma matriz:

results = pool.starmap(function, zip(itertools.repeat(constant), list_a))

Se você estiver usando uma versão anterior do Python, poderá passar vários argumentos por meio desta solução alternativa ).

(Obrigado a user136036 pelo comentário útil.)

philshem
fonte
90
Faltam apenas votos porque foram publicados recentemente. Esta resposta funciona lindamente e demonstra a funcionalidade 'map', que oferece uma sintaxe muito mais fácil de entender do que as outras respostas aqui.
idle
25
Isso é mesmo threads e não processos? Parece que ele tenta Multiprocesso = multithread!
AturSams
72
A propósito, pessoal, você pode escrever with Pool(8) as p: p.map( *whatever* )e se livrar das linhas de contabilidade também.
11
@BarafuAlbino: Por mais útil que seja, provavelmente vale a pena notar que isso só funciona no Python 3.3 ou superior .
Fuglede
9
Como você pode deixar essa resposta e não mencionar que isso é útil apenas para operações de E / S? Isso só funciona em um único segmento que é inútil para a maioria dos casos, e é realmente mais lento do que apenas fazê-lo da maneira normal
Frobot
714

Aqui está um exemplo simples: você precisa tentar alguns URLs alternativos e retornar o conteúdo do primeiro para responder.

import Queue
import threading
import urllib2

# Called by each thread
def get_url(q, url):
    q.put(urllib2.urlopen(url).read())

theurls = ["http://google.com", "http://yahoo.com"]

q = Queue.Queue()

for u in theurls:
    t = threading.Thread(target=get_url, args = (q,u))
    t.daemon = True
    t.start()

s = q.get()
print s

É um caso em que o encadeamento é usado como uma otimização simples: cada sub-rosca está aguardando uma URL resolver e responder, a fim de colocar seu conteúdo na fila; cada thread é um daemon (não continuará o processo se o thread principal terminar - isso é mais comum do que não); o encadeamento principal inicia todos os sub-roscas, faz um getna fila para aguardar até que um deles faça um e put, em seguida, emite os resultados e termina (o que elimina quaisquer sub-roscas que ainda possam estar em execução, já que são encadeamentos daemon).

O uso adequado de threads no Python é invariavelmente conectado a operações de E / S (como o CPython não usa vários núcleos para executar tarefas ligadas à CPU de qualquer maneira, a única razão para o encadeamento não é bloquear o processo enquanto há uma espera por algumas E / S ) As filas são quase sempre a melhor maneira de aumentar o trabalho para threads e / ou coletar os resultados do trabalho, a propósito, e são intrinsecamente seguros para threads, portanto, evitam que você se preocupe com bloqueios, condições, eventos, semáforos e outros conceitos de coordenação / comunicação.

Alex Martelli
fonte
10
Mais uma vez obrigado, MartelliBot. Atualizei o exemplo para aguardar que todos os URLs respondam: import Queue, threading, urllib2 q = Queue.Queue () urls = '' ' a.com b.com c.com' ''. Split () urls_received = 0 def get_url (q, url): req = urllib2.Request (url) resp = urllib2.urlopen (req) q.put (resp.read ()) global urls_received urls_received + = 1 imprimir urls_received para u em urls: t = threading.Thread (target = get_url, args = (q, u)) t.daemon = True t.start () enquanto q.empty () e urls_received <len (urls): s = q.get () print s
Htmldrum
3
@JRM: se você olhar para a próxima resposta abaixo, acho que a melhor maneira de esperar até que os threads sejam finalizados seria usar o join()método, pois isso faria o thread principal aguardar até que sejam concluídos sem consumir o processador constantemente verificando o valor. @ Alex: obrigado, é exatamente isso que eu precisava para entender como usar threads.
Krs013 31/05
6
Para python3, substitua 'import urllib2' por 'import urllib.request as urllib2'. e coloque parênteses na declaração de impressão.
Harvey
5
Para python 3, substitua o Queuenome do módulo por queue. O nome do método é o mesmo.
precisa saber é o seguinte
2
Observo que a solução imprimirá apenas uma das páginas. Para imprimir as duas páginas da fila, basta executar o comando novamente: s = q.get() print s @ krs013 Não é necessário joinporque o Queue.get () está bloqueando.
Tom Anderson
256

NOTA : Para paralelização real no Python, você deve usar o módulo de multiprocessamento para dividir vários processos que são executados em paralelo (devido ao bloqueio global do interpretador, os threads do Python fornecem intercalação, mas na verdade são executados em série, não em paralelo e são apenas útil ao intercalar operações de E / S).

No entanto, se você está apenas procurando intercalação (ou está executando operações de E / S que podem ser paralelizadas apesar do bloqueio global do intérprete), o módulo de encadeamento é o ponto de partida. Como um exemplo realmente simples, vamos considerar o problema de somar um grande intervalo somando subintervalos em paralelo:

import threading

class SummingThread(threading.Thread):
     def __init__(self,low,high):
         super(SummingThread, self).__init__()
         self.low=low
         self.high=high
         self.total=0

     def run(self):
         for i in range(self.low,self.high):
             self.total+=i


thread1 = SummingThread(0,500000)
thread2 = SummingThread(500000,1000000)
thread1.start() # This actually causes the thread to run
thread2.start()
thread1.join()  # This waits until the thread has completed
thread2.join()
# At this point, both threads have completed
result = thread1.total + thread2.total
print result

Observe que o acima é um exemplo muito estúpido, pois não faz absolutamente nenhuma E / S e será executado serialmente, embora intercalado (com a sobrecarga adicional da alternância de contexto) no CPython devido ao bloqueio global do interpretador.

Michael Aaron Safyan
fonte
16
@ Alex, eu não disse que era prático, mas demonstra como definir e gerar threads, que eu acho que é o que o OP quer.
Michael Aaron Safyan
6
Embora isso mostre como definir e gerar threads, na verdade não soma as subintervalos em paralelo. thread1é executado até que seja concluído enquanto o encadeamento principal bloqueia, o mesmo acontece com thread2o encadeamento principal e retoma e imprime os valores acumulados.
22413 martineau
Não deveria ser super(SummingThread, self).__init__()? Como em stackoverflow.com/a/2197625/806988
James Andres
@JamesAndres, supondo que ninguém herda de "SummingThread", então qualquer um deles funciona bem; nesse caso, super (SummingThread, self) é apenas uma maneira elegante de procurar a próxima classe na ordem de resolução de método (MRO), que é threading.Thread (e, posteriormente, chamando init para isso nos dois casos). Você está certo, porém, de que usar super () é um estilo melhor para o Python atual. Super era relativamente recente no momento em que forneci essa resposta, portanto, chamei diretamente a superclasse em vez de usar super (). Vou atualizar isso para usar super, no entanto.
Michael Aaron Safyan
14
AVISO: Não use multithreading em tarefas como esta! Como foi mostrado por Dave Beazley: dabeaz.com/python/NewGIL.pdf , 2 threads python em 2 CPUs realizam uma tarefa pesada com CPU 2 vezes MAIS LENTA que 1 thread em 1 CPU e 1,5 vezes MAIS LENTA que 2 threads em 1 CPU. Esse comportamento bizarro é devido à falta de coordenação dos esforços entre o SO e o Python. Um caso de uso da vida real para encadeamentos é uma tarefa pesada de E / S. Por exemplo, quando você executa leituras / gravações na rede, faz sentido colocar um encadeamento, aguardando a leitura / gravação dos dados, em segundo plano e alternar a CPU para outro encadeamento, que precisa processar os dados.
Boris Burkov 15/05
98

Como outros mencionados, o CPython pode usar threads apenas para esperas de E / S devido ao GIL .

Se você deseja se beneficiar de vários núcleos para tarefas ligadas à CPU, use o multiprocessamento :

from multiprocessing import Process

def f(name):
    print 'hello', name

if __name__ == '__main__':
    p = Process(target=f, args=('bob',))
    p.start()
    p.join()
Kai
fonte
33
você poderia explicar um pouco o que isso faz?
pandita 14/09
5
@ Pandita: o código cria um processo e o inicia. Portanto, agora há duas coisas acontecendo ao mesmo tempo: a linha principal do programa e o processo que está começando com o objetivo, ffunção. Paralelamente, o programa principal agora aguarda a saída do processo, joinseguindo-o. Se a parte principal acabou de sair, o subprocesso pode ou não ser executado até a conclusão, portanto, joiné recomendável fazer isso .
21468 John Mayer
1
Uma resposta alargada, que inclui a mapfunção é aqui: stackoverflow.com/a/28463266/2327328
philshem
2
@philshem Tenha cuidado porque o link que você postou está usando um pool de threads (não processos), como mencionado aqui stackoverflow.com/questions/26432411/… . No entanto, esta resposta está usando um processo. Eu sou novo nessas coisas, mas parece que (devido ao GIL) você só obterá ganhos de desempenho em situações específicas ao usar multithreading em Python. No entanto, o uso de um conjunto de processos pode tirar proveito de um processador multicore por ter mais de um trabalho principal em um processo.
user3731622
3
Esta é a melhor resposta para realmente fazer algo útil e aproveitando vários núcleos de CPU
Frobot
92

Apenas uma observação: uma fila não é necessária para segmentação.

Este é o exemplo mais simples que eu poderia imaginar que mostra 10 processos em execução simultaneamente.

import threading
from random import randint
from time import sleep


def print_number(number):

    # Sleeps a random 1 to 10 seconds
    rand_int_var = randint(1, 10)
    sleep(rand_int_var)
    print "Thread " + str(number) + " slept for " + str(rand_int_var) + " seconds"

thread_list = []

for i in range(1, 10):

    # Instantiates the thread
    # (i) does not make a sequence, so (i,)
    t = threading.Thread(target=print_number, args=(i,))
    # Sticks the thread in a list so that it remains accessible
    thread_list.append(t)

# Starts threads
for thread in thread_list:
    thread.start()

# This blocks the calling thread until the thread whose join() method is called is terminated.
# From http://docs.python.org/2/library/threading.html#thread-objects
for thread in thread_list:
    thread.join()

# Demonstrates that the main process waited for threads to complete
print "Done"
Douglas Adams
fonte
3
Adicione a última citação a "Concluído para imprimir" Concluído "
iChux 11/02/14
1
Gosto mais deste exemplo que o de Martelli, é mais fácil brincar. No entanto, eu recomendaria que printNumber faça o seguinte, para tornar um pouco mais claro o que está acontecendo: ele deve salvar a randint em uma variável antes de dormir nela e, em seguida, a impressão deve ser alterada para dizer "Thread" + str ( number) + "dormiu por" + theRandintVariable + "segundos"
Nickolai
Existe uma maneira de saber quando cada thread terminou, como termina?
Matt
1
@ Matt Existem algumas maneiras de fazer algo assim, mas isso depende de suas necessidades. Uma maneira seria atualizar um singleton ou alguma outra variável acessível ao público que está sendo observada em um loop while e atualizada no final do thread.
Douglas Adams
2
Não há necessidade de segundo forloop, você pode chamar thread.start()no primeiro loop.
MarkMishyn
49

A resposta de Alex Martelli me ajudou. No entanto, aqui está uma versão modificada que achei mais útil (pelo menos para mim).

Atualizado: funciona em Python 2 e Python 3

try:
    # For Python 3
    import queue
    from urllib.request import urlopen
except:
    # For Python 2 
    import Queue as queue
    from urllib2 import urlopen

import threading

worker_data = ['http://google.com', 'http://yahoo.com', 'http://bing.com']

# Load up a queue with your data. This will handle locking
q = queue.Queue()
for url in worker_data:
    q.put(url)

# Define a worker function
def worker(url_queue):
    queue_full = True
    while queue_full:
        try:
            # Get your data off the queue, and do some work
            url = url_queue.get(False)
            data = urlopen(url).read()
            print(len(data))

        except queue.Empty:
            queue_full = False

# Create as many threads as you want
thread_count = 5
for i in range(thread_count):
    t = threading.Thread(target=worker, args = (q,))
    t.start()
JimJty
fonte
6
Por que não apenas quebrar a exceção?
Stavros Korokithakis
1
você poderia, apenas preferência pessoal
JimJty 10/02
1
Não executei o código, mas você não precisa daemonizar os threads? Eu acho que depois desse último loop for, seu programa pode sair - pelo menos deveria, porque é assim que os threads devem funcionar. Eu acho que uma abordagem melhor não é colocar os dados do trabalhador na fila, mas colocar a saída em uma fila, porque você pode ter um loop principal que não apenas lide com as informações que entram na fila dos trabalhadores, mas agora também não é o threading, e você sabe que não sairá prematuramente.
Dllnmc
1
@dylnmc, isso está fora do meu caso de uso (minha fila de entrada é predefinida). Se você quer ir a sua rota, gostaria de sugerir a olhar para o aipo
JimJty
@JimJty você sabe por que eu estou recebendo este erro: import Queue ModuleNotFoundError: No module named 'Queue'Estou executando python 3.6.5 algumas mensagens mencionar que em python 3.6.5 ele é queue, mas mesmo depois de eu mudar isso, ainda não funciona
user9371654
25

Dada uma função, frosqueie-a assim:

import threading
threading.Thread(target=f).start()

Para passar argumentos para f

threading.Thread(target=f, args=(a,b,c)).start()
starfry
fonte
Isso é muito direto. Como você garante que os threads fechem quando você terminar com eles?
Cameronroytaylor 5/05
Pelo que entendi, quando a função sai, o Threadobjeto é limpo. Veja os documentos . Existe um is_alive()método que você pode usar para verificar um encadeamento, se necessário.
Starfry # 6/17
Eu vi o is_alivemétodo, mas não consegui descobrir como aplicá-lo ao thread. Tentei atribuir thread1=threading.Thread(target=f).start()e, em seguida, verificá-lo com thread1.is_alive(), mas thread1é preenchido com None, então não há sorte lá. Você sabe se existe alguma outra maneira de acessar o thread?
Cameronroytaylor
4
Você precisa atribuir o objeto de encadeamento a uma variável e, em seguida, iniciá-lo usando essa variável: thread1=threading.Thread(target=f)seguida por thread1.start(). Então você pode fazer thread1.is_alive().
starfry
1
Isso funcionou. E sim, testando com thread1.is_alive()retornos Falseassim que a função sair.
Cameronroytaylor #
25

Achei isso muito útil: crie tantos threads quanto núcleos e permita que eles executem um número (grande) de tarefas (nesse caso, chamando um programa shell):

import Queue
import threading
import multiprocessing
import subprocess

q = Queue.Queue()
for i in range(30): # Put 30 tasks in the queue
    q.put(i)

def worker():
    while True:
        item = q.get()
        # Execute a task: call a shell program and wait until it completes
        subprocess.call("echo " + str(item), shell=True)
        q.task_done()

cpus = multiprocessing.cpu_count() # Detect number of cores
print("Creating %d threads" % cpus)
for i in range(cpus):
     t = threading.Thread(target=worker)
     t.daemon = True
     t.start()

q.join() # Block until all tasks are done
golfinho
fonte
@ shavenwarthog Certifique-se de que é possível ajustar a variável "cpus", dependendo das necessidades. De qualquer forma, a chamada de subprocesso gerará subprocessos e esses serão atribuídos cpus pelo SO ("processo pai" do python não significa "mesma CPU" para os subprocessos).
dolphin
2
você está correto, meu comentário sobre "threads iniciados na mesma CPU que o processo pai" está errado. Obrigado pela resposta!
21468 John Mayer
1
talvez seja interessante notar que, diferentemente do multithreading, que utiliza o mesmo espaço de memória, o multiprocessamento não pode compartilhar variáveis ​​/ dados tão facilmente. +1 embora.
fantabolous
22

O Python 3 tem a facilidade de iniciar tarefas paralelas . Isso facilita nosso trabalho.

Possui pool de threads e pool de processos .

A seguir, é apresentado um insight:

Exemplo de ThreadPoolExecutor ( origem )

import concurrent.futures
import urllib.request

URLS = ['http://www.foxnews.com/',
        'http://www.cnn.com/',
        'http://europe.wsj.com/',
        'http://www.bbc.co.uk/',
        'http://some-made-up-domain.com/']

# Retrieve a single page and report the URL and contents
def load_url(url, timeout):
    with urllib.request.urlopen(url, timeout=timeout) as conn:
        return conn.read()

# We can use a with statement to ensure threads are cleaned up promptly
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
    # Start the load operations and mark each future with its URL
    future_to_url = {executor.submit(load_url, url, 60): url for url in URLS}
    for future in concurrent.futures.as_completed(future_to_url):
        url = future_to_url[future]
        try:
            data = future.result()
        except Exception as exc:
            print('%r generated an exception: %s' % (url, exc))
        else:
            print('%r page is %d bytes' % (url, len(data)))

ProcessPoolExecutor ( origem )

import concurrent.futures
import math

PRIMES = [
    112272535095293,
    112582705942171,
    112272535095293,
    115280095190773,
    115797848077099,
    1099726899285419]

def is_prime(n):
    if n % 2 == 0:
        return False

    sqrt_n = int(math.floor(math.sqrt(n)))
    for i in range(3, sqrt_n + 1, 2):
        if n % i == 0:
            return False
    return True

def main():
    with concurrent.futures.ProcessPoolExecutor() as executor:
        for number, prime in zip(PRIMES, executor.map(is_prime, PRIMES)):
            print('%d is prime: %s' % (number, prime))

if __name__ == '__main__':
    main()
Jeril
fonte
18

Usando o novo módulo concurrent.futures

def sqr(val):
    import time
    time.sleep(0.1)
    return val * val

def process_result(result):
    print(result)

def process_these_asap(tasks):
    import concurrent.futures

    with concurrent.futures.ProcessPoolExecutor() as executor:
        futures = []
        for task in tasks:
            futures.append(executor.submit(sqr, task))

        for future in concurrent.futures.as_completed(futures):
            process_result(future.result())
        # Or instead of all this just do:
        # results = executor.map(sqr, tasks)
        # list(map(process_result, results))

def main():
    tasks = list(range(10))
    print('Processing {} tasks'.format(len(tasks)))
    process_these_asap(tasks)
    print('Done')
    return 0

if __name__ == '__main__':
    import sys
    sys.exit(main())

A abordagem do executor pode parecer familiar para todos aqueles que já sujaram as mãos com Java antes.

Também em uma nota lateral: para manter o universo saudável, não se esqueça de fechar seus pools / executores se você não usar o withcontexto (o que é tão incrível que faz isso por você)

Shubham Chaudhary
fonte
17

Para mim, o exemplo perfeito de encadeamento é o monitoramento de eventos assíncronos. Veja este código.

# thread_test.py
import threading
import time

class Monitor(threading.Thread):
    def __init__(self, mon):
        threading.Thread.__init__(self)
        self.mon = mon

    def run(self):
        while True:
            if self.mon[0] == 2:
                print "Mon = 2"
                self.mon[0] = 3;

Você pode jogar com esse código abrindo uma sessão IPython e fazendo algo como:

>>> from thread_test import Monitor
>>> a = [0]
>>> mon = Monitor(a)
>>> mon.start()
>>> a[0] = 2
Mon = 2
>>>a[0] = 2
Mon = 2

Espere alguns minutos

>>> a[0] = 2
Mon = 2
dvreed77
fonte
1
AttributeError: o objeto 'Monitor' não tem atributo 'stop'?
pandita
5
Você não está destruindo os ciclos da CPU enquanto aguarda o seu evento? Nem sempre é uma coisa muito prática a se fazer.
mogul
3
Como diz o magnata, isso será executado constantemente. No mínimo, você pode adicionar um sono curto, digamos sleep (0.1), o que provavelmente reduziria significativamente o uso da CPU em um exemplo simples como este.
fantabolous
3
Este é um exemplo horrível, desperdiçando um núcleo. Adicione um sono no mínimo, mas a solução adequada é usar algum mecanismo de sinalização.
PureW
16

A maioria da documentação e dos tutoriais usa o Python Threadinge o Queuemódulo, e eles podem parecer impressionantes para iniciantes.

Talvez considere o concurrent.futures.ThreadPoolExecutormódulo do Python 3.

Combinado com a compreensão de withcláusulas e listas, pode ser um verdadeiro encanto.

from concurrent.futures import ThreadPoolExecutor, as_completed

def get_url(url):
    # Your actual program here. Using threading.Lock() if necessary
    return ""

# List of URLs to fetch
urls = ["url1", "url2"]

with ThreadPoolExecutor(max_workers = 5) as executor:

    # Create threads
    futures = {executor.submit(get_url, url) for url in urls}

    # as_completed() gives you the threads once finished
    for f in as_completed(futures):
        # Get the results
        rs = f.result()
Yibo
fonte
15

Eu vi muitos exemplos aqui em que nenhum trabalho real estava sendo realizado, e eles eram principalmente ligados à CPU. Aqui está um exemplo de uma tarefa vinculada à CPU que calcula todos os números primos entre 10 e 10,05 milhões. Eu usei todos os quatro métodos aqui:

import math
import timeit
import threading
import multiprocessing
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor


def time_stuff(fn):
    """
    Measure time of execution of a function
    """
    def wrapper(*args, **kwargs):
        t0 = timeit.default_timer()
        fn(*args, **kwargs)
        t1 = timeit.default_timer()
        print("{} seconds".format(t1 - t0))
    return wrapper

def find_primes_in(nmin, nmax):
    """
    Compute a list of prime numbers between the given minimum and maximum arguments
    """
    primes = []

    # Loop from minimum to maximum
    for current in range(nmin, nmax + 1):

        # Take the square root of the current number
        sqrt_n = int(math.sqrt(current))
        found = False

        # Check if the any number from 2 to the square root + 1 divides the current numnber under consideration
        for number in range(2, sqrt_n + 1):

            # If divisible we have found a factor, hence this is not a prime number, lets move to the next one
            if current % number == 0:
                found = True
                break

        # If not divisible, add this number to the list of primes that we have found so far
        if not found:
            primes.append(current)

    # I am merely printing the length of the array containing all the primes, but feel free to do what you want
    print(len(primes))

@time_stuff
def sequential_prime_finder(nmin, nmax):
    """
    Use the main process and main thread to compute everything in this case
    """
    find_primes_in(nmin, nmax)

@time_stuff
def threading_prime_finder(nmin, nmax):
    """
    If the minimum is 1000 and the maximum is 2000 and we have four workers,
    1000 - 1250 to worker 1
    1250 - 1500 to worker 2
    1500 - 1750 to worker 3
    1750 - 2000 to worker 4
    so let’s split the minimum and maximum values according to the number of workers
    """
    nrange = nmax - nmin
    threads = []
    for i in range(8):
        start = int(nmin + i * nrange/8)
        end = int(nmin + (i + 1) * nrange/8)

        # Start the thread with the minimum and maximum split up to compute
        # Parallel computation will not work here due to the GIL since this is a CPU-bound task
        t = threading.Thread(target = find_primes_in, args = (start, end))
        threads.append(t)
        t.start()

    # Don’t forget to wait for the threads to finish
    for t in threads:
        t.join()

@time_stuff
def processing_prime_finder(nmin, nmax):
    """
    Split the minimum, maximum interval similar to the threading method above, but use processes this time
    """
    nrange = nmax - nmin
    processes = []
    for i in range(8):
        start = int(nmin + i * nrange/8)
        end = int(nmin + (i + 1) * nrange/8)
        p = multiprocessing.Process(target = find_primes_in, args = (start, end))
        processes.append(p)
        p.start()

    for p in processes:
        p.join()

@time_stuff
def thread_executor_prime_finder(nmin, nmax):
    """
    Split the min max interval similar to the threading method, but use a thread pool executor this time.
    This method is slightly faster than using pure threading as the pools manage threads more efficiently.
    This method is still slow due to the GIL limitations since we are doing a CPU-bound task.
    """
    nrange = nmax - nmin
    with ThreadPoolExecutor(max_workers = 8) as e:
        for i in range(8):
            start = int(nmin + i * nrange/8)
            end = int(nmin + (i + 1) * nrange/8)
            e.submit(find_primes_in, start, end)

@time_stuff
def process_executor_prime_finder(nmin, nmax):
    """
    Split the min max interval similar to the threading method, but use the process pool executor.
    This is the fastest method recorded so far as it manages process efficiently + overcomes GIL limitations.
    RECOMMENDED METHOD FOR CPU-BOUND TASKS
    """
    nrange = nmax - nmin
    with ProcessPoolExecutor(max_workers = 8) as e:
        for i in range(8):
            start = int(nmin + i * nrange/8)
            end = int(nmin + (i + 1) * nrange/8)
            e.submit(find_primes_in, start, end)

def main():
    nmin = int(1e7)
    nmax = int(1.05e7)
    print("Sequential Prime Finder Starting")
    sequential_prime_finder(nmin, nmax)
    print("Threading Prime Finder Starting")
    threading_prime_finder(nmin, nmax)
    print("Processing Prime Finder Starting")
    processing_prime_finder(nmin, nmax)
    print("Thread Executor Prime Finder Starting")
    thread_executor_prime_finder(nmin, nmax)
    print("Process Executor Finder Starting")
    process_executor_prime_finder(nmin, nmax)

main()

Aqui estão os resultados na minha máquina com quatro núcleos Mac OS X

Sequential Prime Finder Starting
9.708213827005238 seconds
Threading Prime Finder Starting
9.81836523200036 seconds
Processing Prime Finder Starting
3.2467174359990167 seconds
Thread Executor Prime Finder Starting
10.228896902000997 seconds
Process Executor Finder Starting
2.656402041000547 seconds
PirateApp
fonte
1
@TheUnfunCat nenhum executor processo é muito melhor do que enfiar para tarefas vinculado à CPU
PirateApp
1
Ótima resposta, cara. Posso confirmar que no Python 3.6 no Windows (pelo menos) o ThreadPoolExecutor não faz nada de bom para tarefas pesadas na CPU. Não está utilizando núcleos para computação. Enquanto ProcessPoolExecutor copia dados em TODOS os processos que gera, é mortal para matrizes grandes.
Anatoly Alekseev
1
Exemplo muito útil, mas não entendo como isso funcionou. Precisamos de um if __name__ == '__main__':antes da chamada principal, caso contrário, as desovas medição em si e gravuras Uma tentativa foi feita para iniciar um novo processo antes ... .
Stein
1
@ Stan Eu acredito que isso é apenas um problema no Windows, no entanto.
AMC
12

Aqui está um exemplo muito simples de importação de CSV usando threading. (A inclusão da biblioteca pode diferir para propósitos diferentes.)

Funções auxiliares:

from threading import Thread
from project import app
import csv


def import_handler(csv_file_name):
    thr = Thread(target=dump_async_csv_data, args=[csv_file_name])
    thr.start()

def dump_async_csv_data(csv_file_name):
    with app.app_context():
        with open(csv_file_name) as File:
            reader = csv.DictReader(File)
            for row in reader:
                # DB operation/query

Função do driver:

import_handler(csv_file_name)
Chirag Vora
fonte
9

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)
Pitto
fonte
7

Aqui está o multi-threading com um exemplo simples que será útil. Você pode executá-lo e entender facilmente como o multi threading está funcionando no Python. Usei uma trava para impedir o acesso a outros threads até que os threads anteriores terminassem seu trabalho. Pelo uso dessa linha de código,

tLock = threading.BoundedSemaphore (valor = 4)

você pode permitir vários processos por vez e manter o restante dos encadeamentos que serão executados mais tarde ou depois dos processos anteriores.

import threading
import time

#tLock = threading.Lock()
tLock = threading.BoundedSemaphore(value=4)
def timer(name, delay, repeat):
    print  "\r\nTimer: ", name, " Started"
    tLock.acquire()
    print "\r\n", name, " has the acquired the lock"
    while repeat > 0:
        time.sleep(delay)
        print "\r\n", name, ": ", str(time.ctime(time.time()))
        repeat -= 1

    print "\r\n", name, " is releaseing the lock"
    tLock.release()
    print "\r\nTimer: ", name, " Completed"

def Main():
    t1 = threading.Thread(target=timer, args=("Timer1", 2, 5))
    t2 = threading.Thread(target=timer, args=("Timer2", 3, 5))
    t3 = threading.Thread(target=timer, args=("Timer3", 4, 5))
    t4 = threading.Thread(target=timer, args=("Timer4", 5, 5))
    t5 = threading.Thread(target=timer, args=("Timer5", 0.1, 5))

    t1.start()
    t2.start()
    t3.start()
    t4.start()
    t5.start()

    print "\r\nMain Complete"

if __name__ == "__main__":
    Main()
cSharma
fonte
5

Com o empréstimo desta publicação , sabemos sobre a escolha entre multithreading, multiprocessing e async / asyncioe seu uso.

O Python 3 possui uma nova biblioteca interna para simultaneidade e paralelismo: concurrent.futures

Então, demonstrarei, através de um experimento, a execução de quatro tarefas (ou seja, .sleep()método) da seguinte Threading-Poolmaneira:

from concurrent.futures import ThreadPoolExecutor, as_completed
from time import sleep, time

def concurrent(max_worker=1):
    futures = []

    tick = time()
    with ThreadPoolExecutor(max_workers=max_worker) as executor:
        futures.append(executor.submit(sleep, 2))  # Two seconds sleep
        futures.append(executor.submit(sleep, 1))
        futures.append(executor.submit(sleep, 7))
        futures.append(executor.submit(sleep, 3))

        for future in as_completed(futures):
            if future.result() is not None:
                print(future.result())

    print('Total elapsed time by {} workers:'.format(max_worker), time()-tick)

concurrent(5)
concurrent(4)
concurrent(3)
concurrent(2)
concurrent(1)

Resultado:

Total elapsed time by 5 workers: 7.007831811904907
Total elapsed time by 4 workers: 7.007944107055664
Total elapsed time by 3 workers: 7.003149509429932
Total elapsed time by 2 workers: 8.004627466201782
Total elapsed time by 1 workers: 13.013478994369507

[ NOTA ]:

  • Como você pode ver nos resultados acima, o melhor caso foi de três trabalhadores para essas quatro tarefas.
  • Se você tiver uma tarefa de processo em vez de limite de E / S ou bloqueio ( multiprocessingvs threading), poderá alterar ThreadPoolExecutorpara ProcessPoolExecutor.
Benyamin Jafari
fonte
4

Nenhuma das soluções anteriores realmente usou vários núcleos no meu servidor GNU / Linux (onde eu não tenho direitos de administrador). Eles apenas rodavam em um único núcleo.

Eu usei a os.forkinterface de nível inferior para gerar vários processos. Este é o código que funcionou para mim:

from os import fork

values = ['different', 'values', 'for', 'threads']

for i in range(len(values)):
    p = fork()
    if p == 0:
        my_function(values[i])
        break
David Schumann
fonte
2
import threading
import requests

def send():

  r = requests.get('https://www.stackoverlow.com')

thread = []
t = threading.Thread(target=send())
thread.append(t)
t.start()
Skiller Dz
fonte
1
@sP_ Acho que sim, porque você tem objetos de thread para poder esperar que eles terminem.
Aleksandar Makragić
1
t = threading.Thread (target = send ()) deve ser t = threading.Thread (target = send)
Trine
Estou votando negativamente nesta resposta porque ela não fornece uma explicação de como ela melhora as respostas existentes, além de conter uma imprecisão séria.
Jules