Çok işlem havuzuna benzer diş havuzu mu?


347

İşleyici iş parçacıkları için, çok işlem modülünün Havuz sınıfına benzer bir Pool sınıfı var mı?

Örneğin bir harita fonksiyonunu paralelleştirmenin kolay yolunu seviyorum

def long_running_func(p):
    c_func_no_gil(p)

p = multiprocessing.Pool(4)
xs = p.map(long_running_func, range(100))

ancak bunu yeni süreçler yaratma yükü olmadan yapmak istiyorum.

GIL'i biliyorum. Ancak, benim usase içinde, işlev Python sarıcı gerçek işlev çağrısından önce GIL serbest bırakacağı bir IO bağlı C işlevi olacaktır.

Kendi iş parçacığı havuzumu yazmak zorunda mıyım?


Görünüyor Python Cookbook'ta üzerinde umut verici olduğunu İşte en şey: Reçete 576519: Konu havuzu aynı API ile olduğu gibi (çoklu) processing.Pool (Python)
otherchirps

1
Günümüzde yerleşik bulunuyor: from multiprocessing.pool import ThreadPool.
martineau

Bu konuyu biraz açıklayabilir misiniz I know about the GIL. However, in my usecase, the function will be an IO-bound C function for which the python wrapper will release the GIL before the actual function call.?
1919

Yanıtlar:


448

Sadece aslında orada öğrendim olduğu bir iplik tabanlı Havuz arayüzü multiprocessingancak biraz gizlidir ve düzgün belgelenmiş değil, modül.

Üzerinden ithal edilebilir

from multiprocessing.pool import ThreadPool

Bir python iş parçacığını saran bir kukla Process sınıfı kullanılarak uygulanır. Bu iş parçacığı tabanlı Process sınıfı multiprocessing.dummy, dokümanlarda kısaca belirtilmiş olarak bulunabilir . Bu kukla modül, sözde ipliklere dayalı tüm çoklu işlem arayüzünü sağlar.


5
Bu harika. Ana iş parçacığı dışında ThreadPools oluşturmada bir sorun vardı, bir kez olsa da oluşturulan bir çocuk iş parçacığı kullanabilirsiniz. Bunun için bir sorun koydum: bugs.python.org/issue10015
Olson

82
Neden bu sınıfta hiçbir belge yok anlamıyorum. Bu tür yardımcı sınıflar günümüzde çok önemlidir.
Wernight

18
@Wernight: Öncelikle herkese açık değildir, çünkü kimse ona iplik geçirme (veya benzer bir şey) sağlayan bir yama sunmamıştır.ThreadPool, belgeler ve testler dahil. Gerçekten standart kütüphaneye dahil etmek iyi bir pil olurdu, ancak kimse yazmazsa olmaz. Çoklu işlemdeki bu mevcut uygulamanın güzel bir avantajı, bu tür herhangi bir iplik düzeltme ekinin yazılmasını çok daha kolay hale getirmesidir ( docs.python.org/devguide )
ncoghlan

3
@ daniel.gindi: multiprocessing.dummy.Pool/ multiprocessing.pool.ThreadPoolaynı şeydir ve her ikisi de iş parçacığı havuzlarıdır. Bir işlem havuzunun arayüzünü taklit ederler , ancak tamamen diş açma açısından uygulanırlar. Dokümanları tekrar okuyun, geriye doğru aldınız.
ShadowRanger

9
@ daniel.gindi: Daha fazlasını okuyun : " modülünün multiprocessing.dummyAPI'sini çoğaltır, multiprocessingancak threadingmodülün etrafındaki bir paketten başka bir şey değildir ." multiprocessinggenel olarak süreçlerle ilgilidir, ancak süreçler ve iş parçacıkları arasında geçişe izin vermek için, (çoğunlukla) multiprocessingAPI'yi multiprocessing.dummyişlemlerde değil, iş parçacıklarında desteklediler. Amaç, import multiprocessing.dummy as multiprocessingişlem tabanlı kodu iş parçacığı tabanlı olarak değiştirmenize izin vermektir .
ShadowRanger


63

Evet ve aynı API'ya (aşağı yukarı) sahip gibi görünüyor.

import multiprocessing

def worker(lnk):
    ....    
def start_process():
    .....
....

if(PROCESS):
    pool = multiprocessing.Pool(processes=POOL_SIZE, initializer=start_process)
else:
    pool = multiprocessing.pool.ThreadPool(processes=POOL_SIZE, 
                                           initializer=start_process)

pool.map(worker, inputs)
....

9
İçin içe aktarma yolu ThreadPoolbundan farklı Pool. Doğru içe aktarma from multiprocessing.pool import ThreadPool.
Marigold

2
Garip bir şekilde bu bir belgelenmiş API değildir ve multiprocessing.pool sadece kısaca AsyncResult sağladığı belirtilmektedir. Ancak 2.x ve 3.x sürümlerinde mevcuttur.
Marvin

2
Aradığım şey buydu. Bu sadece tek bir ithalat hattı ve mevcut havuz hattımda küçük bir değişiklik ve mükemmel çalışıyor.
Danegraphics

39

Çok basit ve hafif bir şey için ( buradan biraz değiştirildi ):

from Queue import Queue
from threading import Thread


class Worker(Thread):
    """Thread executing tasks from a given tasks queue"""
    def __init__(self, tasks):
        Thread.__init__(self)
        self.tasks = tasks
        self.daemon = True
        self.start()

    def run(self):
        while True:
            func, args, kargs = self.tasks.get()
            try:
                func(*args, **kargs)
            except Exception, e:
                print e
            finally:
                self.tasks.task_done()


class ThreadPool:
    """Pool of threads consuming tasks from a queue"""
    def __init__(self, num_threads):
        self.tasks = Queue(num_threads)
        for _ in range(num_threads):
            Worker(self.tasks)

    def add_task(self, func, *args, **kargs):
        """Add a task to the queue"""
        self.tasks.put((func, args, kargs))

    def wait_completion(self):
        """Wait for completion of all the tasks in the queue"""
        self.tasks.join()

if __name__ == '__main__':
    from random import randrange
    from time import sleep

    delays = [randrange(1, 10) for i in range(100)]

    def wait_delay(d):
        print 'sleeping for (%d)sec' % d
        sleep(d)

    pool = ThreadPool(20)

    for i, d in enumerate(delays):
        pool.add_task(wait_delay, d)

    pool.wait_completion()

Görev tamamlandığında geri aramaları desteklemek için görev grubuna geri aramayı ekleyebilirsiniz.


koşulsuz sonsuz döngü varsa iş parçacıkları nasıl katılabilir?
Joseph Garvin

@JosephGarvin Test ettim ve Queue.get()program bitene kadar iş parçacıkları boş bir kuyrukta (çağrı engellendiğinden) engellemeye devam ediyor, sonra otomatik olarak sonlandırılıyor.
forumcu

@JosephGarvin, güzel soru. Queue.join()aslında iş parçacığı değil , görev kuyruğuna katılır . Böylece, kuyruk boş olduğunda wait_completion, işletim sistemi tarafından geri döner, program sona erer ve iş parçacıkları toplanır.
randomir

Bu kodun tamamı düzgün bir işleve sarılırsa, kuyruk boş ve pool.wait_completion()geri döndüğünde bile iş parçacıklarını durduruyor gibi görünmüyor . Sonuç, ipliklerin sadece inşa etmeye devam etmesidir.
Ubiquibacon

17

Merhaba Python iş parçacığı havuzunu kullanmak için şu kitaplığı kullanabilirsiniz:

from multiprocessing.dummy import Pool as ThreadPool

ve sonra kullanım için, bu kütüphane böyle:

pool = ThreadPool(threads)
results = pool.map(service, tasks)
pool.close()
pool.join()
return results

İş parçacıkları, istediğiniz iş parçacığı sayısıdır ve görevler, hizmete en çok eşleşen görevlerin listesidir.


Teşekkürler, bu harika bir öneri! Docs dosyasından: multiprocessing.dummy, çoklu işlem API'sini çoğaltır, ancak iş parçacığı modülü etrafındaki bir paketleyiciden başka bir şey değildir. Bir düzeltme - Sanırım havuz api (işlev, yinelenebilir) olduğunu söylemek istiyorum
layser

2
Biz .close()ve cevapsız .join()cevaplar ve .map()tüm konuları bitmeden önce bitirmek için neden olur . Sadece bir uyarı.
Anatoly Scherbakov

8

Sonunda kullandığım sonuç. Yukarıdaki dgorissen tarafından sınıfların değiştirilmiş bir versiyonudur.

Dosya: threadpool.py

from queue import Queue, Empty
import threading
from threading import Thread


class Worker(Thread):
    _TIMEOUT = 2
    """ Thread executing tasks from a given tasks queue. Thread is signalable, 
        to exit
    """
    def __init__(self, tasks, th_num):
        Thread.__init__(self)
        self.tasks = tasks
        self.daemon, self.th_num = True, th_num
        self.done = threading.Event()
        self.start()

    def run(self):       
        while not self.done.is_set():
            try:
                func, args, kwargs = self.tasks.get(block=True,
                                                   timeout=self._TIMEOUT)
                try:
                    func(*args, **kwargs)
                except Exception as e:
                    print(e)
                finally:
                    self.tasks.task_done()
            except Empty as e:
                pass
        return

    def signal_exit(self):
        """ Signal to thread to exit """
        self.done.set()


class ThreadPool:
    """Pool of threads consuming tasks from a queue"""
    def __init__(self, num_threads, tasks=[]):
        self.tasks = Queue(num_threads)
        self.workers = []
        self.done = False
        self._init_workers(num_threads)
        for task in tasks:
            self.tasks.put(task)

    def _init_workers(self, num_threads):
        for i in range(num_threads):
            self.workers.append(Worker(self.tasks, i))

    def add_task(self, func, *args, **kwargs):
        """Add a task to the queue"""
        self.tasks.put((func, args, kwargs))

    def _close_all_threads(self):
        """ Signal all threads to exit and lose the references to them """
        for workr in self.workers:
            workr.signal_exit()
        self.workers = []

    def wait_completion(self):
        """Wait for completion of all the tasks in the queue"""
        self.tasks.join()

    def __del__(self):
        self._close_all_threads()


def create_task(func, *args, **kwargs):
    return (func, args, kwargs)

Havuzu kullanmak için

from random import randrange
from time import sleep

delays = [randrange(1, 10) for i in range(30)]

def wait_delay(d):
    print('sleeping for (%d)sec' % d)
    sleep(d)

pool = ThreadPool(20)
for i, d in enumerate(delays):
    pool.add_task(wait_delay, d)
pool.wait_completion()

Diğer okuyucular için bildirim: Bu kod Python 3 (shebang #!/usr/bin/python3)
Daniel Marschall

Neden değeri kullanıyor for i, d in enumerate(delays):ve sonra görmezden geliyorsunuz i?
martineau

@martineau - muhtemelen sadece ibir koşu sırasında yazdırmak istedikleri gelişimden bir kalıntı .
n1k31t4

Neden create_taskvar? Bu ne için?
MrR

Ben inanamıyorum ve SO 4 oy ile cevap Python ThreadPooling yapmak için bir yoldur. Resmi python dağıtımında Threadpool hala kırık mı? Neyi kaçırıyorum?
MrR

2

Yeni süreçleri yaratmanın yükü, özellikle sadece 4 tanesi olduğunda minimaldir. Bu, uygulamanızın bir performans noktası olduğundan şüpheliyim. Basit tutun, profil oluşturma sonuçlarının nereye ve nerede işaret ettiğine göre optimize edin.


5
Sorgulayan Windows altındaysa (ki ben belirtmediğine inanmıyorum), o zaman süreç artışının önemli bir masraf olabileceğini düşünüyorum. En azından son zamanlarda yaptığım projelerde. :-)
Brandon Rhodes

1

Yerleşik iş parçacığı tabanlı havuz yoktur. Bununla birlikte, Queuesınıfla bir üretici / tüketici kuyruğu uygulamak çok hızlı olabilir .

Gönderen: https://docs.python.org/2/library/queue.html

from threading import Thread
from Queue import Queue
def worker():
    while True:
        item = q.get()
        do_work(item)
        q.task_done()

q = Queue()
for i in range(num_worker_threads):
     t = Thread(target=worker)
     t.daemon = True
     t.start()

for item in source():
    q.put(item)

q.join()       # block until all tasks are done

3
Modülde durum böyle değil concurrent.futures.
Thanatos

11
Bunun artık doğru olduğunu düşünmüyorum. from multiprocessing.pool import ThreadPool
Randall Hunt


0

başka bir yöntem, işlemi kuyruk kuyruğu havuzuna eklemek olabilir

import concurrent.futures
with concurrent.futures.ThreadPoolExecutor(max_workers=cpus) as executor:
    for i in range(0, len(list_of_files) - 1):
        a = executor.submit(loop_files2, i, list_of_files2, mt_list, temp_path, mt_dicto)
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.