मल्टीप्रोसेसिंग पूल के समान थ्रेडिंग पूल?


347

क्या कार्यकर्ता थ्रेड्स के लिए एक पूल क्लास है , जो मल्टीप्रोसेसिंग मॉड्यूल के पूल क्लास के समान है ?

मुझे उदाहरण के लिए एक मानचित्र फ़ंक्शन को समानांतर करने का आसान तरीका पसंद है

def long_running_func(p):
    c_func_no_gil(p)

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

हालाँकि मैं नई प्रक्रियाएँ बनाने के ओवरहेड के बिना करना चाहूँगा।

मुझे GIL के बारे में पता है। हालाँकि, मेरे usecase में, फ़ंक्शन एक IO- बद्ध C फ़ंक्शन होगा, जिसके लिए वास्तविक फ़ंक्शन कॉल से पहले अजगर आवरण GIL जारी करेगा।

क्या मुझे अपना स्वयं का थ्रेडिंग पूल लिखना होगा?


यहाँ कुछ ऐसा है जो पाइथन कुकबुक में होनहार दिखता है: रेसिपी 576519: थ्रेड पूल एक ही एपीआई के साथ (बहु) प्रसंस्करण के रूप में।
पॉल

1
आजकल यह अंतर्निहित है: from multiprocessing.pool import ThreadPool
मार्टीन्यू

क्या आप इस बारे में विस्तार से बता सकते हैं 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.?
मर्ग्लोम

जवाबों:


448

मुझे अभी पता चला है कि मॉड्यूल में वास्तव में एक थ्रेड-आधारित पूल इंटरफ़ेस है multiprocessing, हालांकि यह कुछ हद तक छिपा हुआ है और ठीक से प्रलेखित नहीं है।

इसके माध्यम से आयात किया जा सकता है

from multiprocessing.pool import ThreadPool

यह एक डमी प्रोसेस क्लास का उपयोग करके लागू किया जाता है जो एक अजगर धागे को लपेटता है। यह थ्रेड-आधारित प्रक्रिया वर्ग पाया जा सकता है multiprocessing.dummyजिसमें डॉक्स में संक्षेप में उल्लेख किया गया है । यह डमी मॉड्यूल माना जाता है कि थ्रेड्स के आधार पर पूरे मल्टीप्रोसेसिंग इंटरफ़ेस प्रदान करता है।


5
वह तो कमाल है। मुझे मुख्य थ्रेड के बाहर थ्रेडपूल बनाने में समस्या थी, हालांकि आप एक बार बनाए गए चाइल्ड थ्रेड से उनका उपयोग कर सकते हैं। मैंने इसके लिए एक मुद्दा रखा: Bugs.python.org/issue10015
Olson

82
मुझे यह नहीं मिलता कि इस वर्ग के पास कोई दस्तावेज क्यों नहीं है। ऐसे सहायक वर्ग आजकल बहुत महत्वपूर्ण हैं।
वर्नट

18
@Wernight: यह मुख्य रूप से सार्वजनिक नहीं है क्योंकि किसी ने एक पैच की पेशकश नहीं की है जो इसे (या ऐसा ही कुछ) थ्रेडिंग के रूप में प्रदान करता है। थ्रेडपूल, जिसमें प्रलेखन और परीक्षण शामिल हैं। यह वास्तव में मानक पुस्तकालय में शामिल करने के लिए एक अच्छी बैटरी होगी, लेकिन ऐसा नहीं होगा यदि कोई इसे नहीं लिखता है। मल्टीप्रोसेसिंग में इस मौजूदा कार्यान्वयन का एक अच्छा लाभ यह है कि यह किसी भी ऐसे थ्रेडिंग पैच को लिखने में बहुत आसान बना सकता है ( docs.python.org/devguide )
ncoghlan

3
@ daniel.gindi: multiprocessing.dummy.Pool/ multiprocessing.pool.ThreadPoolएक ही चीज हैं, और दोनों थ्रेड पूल हैं। वे एक प्रक्रिया पूल के इंटरफ़ेस की नकल करते हैं , लेकिन उन्हें पूरी तरह से थ्रेडिंग के संदर्भ में लागू किया जाता है। डॉक्स को रीयर करें, आपको यह पीछे की तरफ मिला।
शैडो रेंजर

9
@ daniel.gindi: आगे पढ़ें : " multiprocessing.dummyAPI की प्रतिकृति बनाता है multiprocessingलेकिन threadingमॉड्यूल के चारों ओर आवरण से अधिक नहीं है ।" multiprocessingसामान्य तौर पर प्रक्रियाओं के बारे में है, लेकिन प्रक्रियाओं और थ्रेड्स के बीच स्विच करने की अनुमति देने के लिए, उन्होंने (ज्यादातर) multiprocessingएपीआई को प्रतिकृति में बदल दिया multiprocessing.dummy, लेकिन थ्रेड्स के साथ समर्थित है, प्रक्रियाएं नहीं। लक्ष्य आपको import multiprocessing.dummy as multiprocessingप्रक्रिया-आधारित कोड को थ्रेड-आधारित में बदलने की अनुमति देता है ।
शैडो रेंजर

236

पायथन 3 में आप उपयोग कर सकते हैं concurrent.futures.ThreadPoolExecutor, अर्थात:

executor = ThreadPoolExecutor(max_workers=10)
a = executor.submit(my_function)

अधिक जानकारी और उदाहरणों के लिए डॉक्स देखें ।


6
आदेश का उपयोग करने के बैकपोर्टेड वायदा मॉड्यूल, चलानेsudo pip install futures
येर

मल्टी प्रोसेसिंग के लिए यह सबसे कुशल और सबसे तेज़ तरीका है
हरितसिंह गोहिल

2
उपयोग करने ThreadPoolExecutorऔर के बीच अंतर क्या है multiprocessing.dummy.Pool?
Jay:

63

हां, और ऐसा लगता है कि एक ही एपीआई है।

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
के लिए आयात पथ ThreadPoolसे भिन्न है Pool। सही आयात है from multiprocessing.pool import ThreadPool
मैरीगोल्ड

2
अजीब तरह से यह एक प्रलेखित एपीआई नहीं है, और मल्टीप्रोसेसिंग.पुल केवल संक्षेप में AsyncResult प्रदान करने के रूप में उल्लेख किया गया है। लेकिन यह 2.x और 3.x में उपलब्ध है।
मार्विन

2
यह वही है जिसे मैं देख रहा था। यह सिर्फ एक आयात लाइन है और मेरे मौजूदा पूल लाइन के लिए एक छोटा सा बदलाव है और यह पूरी तरह से काम करता है।
डेनग्राफिक्स

39

कुछ बहुत ही सरल और हल्के के लिए ( यहाँ से थोड़ा संशोधित ):

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()

कार्य पूर्ण होने पर कॉलबैक का समर्थन करने के लिए, आप केवल कॉलबैक को टास्क टपल में जोड़ सकते हैं।


अगर वे बिना शर्त अनंत लूप लेते हैं तो धागे कभी कैसे जुड़ सकते हैं?
जोसेफ गार्विन

@JosephGarvin मैंने इसका परीक्षण किया है, और Queue.get()कार्यक्रम समाप्त होने तक थ्रेड्स एक खाली कतार (जब कॉल को रोक रही है) पर रोकते रहते हैं, जिसके बाद वे स्वचालित रूप से समाप्त हो जाते हैं।
फोरम्यूलेटर

@ जोसेफगर्विन, अच्छा सवाल। Queue.join()वास्तव में कार्य कतार में शामिल होंगे, न कि श्रमिक सूत्र। इसलिए, जब कतार खाली wait_completionहोती है, तो ओएस द्वारा रिटर्न, प्रोग्राम समाप्त होता है, और थ्रेड्स को फिर से जोड़ा जाता है।
बेतरतीब

यदि इस कोड को सभी एक साफ-सुथरे फ़ंक्शन में लपेटा जाता है, तो ऐसा लगता नहीं है कि कतार खाली होने पर भी थ्रेड को रोकना संभव नहीं है pool.wait_completion()। नतीजा यह है कि धागे सिर्फ निर्माण करते रहते हैं।
ubiquibacon

17

हाय, पायथन में थ्रेड पूल का उपयोग करने के लिए आप इस पुस्तकालय का उपयोग कर सकते हैं:

from multiprocessing.dummy import Pool as ThreadPool

और फिर उपयोग के लिए, यह पुस्तकालय ऐसा करता है:

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

थ्रेड्स थ्रेड्स की संख्या है जो आप चाहते हैं और कार्य उस कार्य की एक सूची है जो सेवा के लिए सबसे अधिक मैप करता है।


धन्यवाद, यह एक बढ़िया सुझाव है! डॉक्स से: multiprocessing.dummy मल्टीप्रोसेसिंग के एपीआई की प्रतिकृति बनाता है, लेकिन थ्रेडिंग मॉड्यूल के चारों ओर आवरण से अधिक नहीं है। एक सुधार - मुझे लगता है कि आप यह कहना चाहते हैं कि पूल
आपी

2
हम याद करते हैं .close()और .join()कॉल करते हैं और .map()सभी थ्रेड्स समाप्त होने से पहले समाप्त होने का कारण बनता है। बस एक चेतावनी।
अनातोली शेरेबाकोव

8

यहाँ परिणाम मैं अंत में उपयोग कर रहा है। यह ऊपर dgorissen द्वारा कक्षाओं का एक संशोधित संस्करण है।

फ़ाइल: 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)

पूल का उपयोग करने के लिए

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()

अन्य पाठकों के लिए एनोटेशन: यह कोड पायथन 3 (शेबंग #!/usr/bin/python3) है
डैनियल मार्शेल

आप उपयोग क्यों करते हैं for i, d in enumerate(delays):और फिर iमूल्य को अनदेखा करते हैं ?
मार्टिउ

@martineau - शायद विकास से एक अवशेष जहां वे शायद iएक रन के दौरान प्रिंट करना चाहते थे ।
n1k31t4

क्यों है create_task? ये किसके लिये है?
17

मैं विश्वास नहीं कर सकता और एसओ पर 4 वोटों के साथ जवाब देना पायथन में थ्रेडपूलिंग करने का तरीका है। आधिकारिक अजगर वितरण में थ्रेडपुल अभी भी टूट गया है? मैं क्या खो रहा हूँ?
मिस्टर

2

नई प्रक्रियाओं को बनाने का ओवरहेड न्यूनतम है, खासकर जब यह उनमें से सिर्फ 4 है। मुझे संदेह है कि यह आपके एप्लिकेशन का प्रदर्शन हॉट स्पॉट है। इसे सरल रखें, ऑप्टिमाइज़ करें जहाँ आपको करना है और जहाँ प्रोफाइलिंग परिणाम इंगित करता है।


5
यदि प्रश्नकर्ता विंडोज के अधीन है (जो मुझे विश्वास नहीं है कि वह निर्दिष्ट है), तो मुझे लगता है कि प्रक्रिया पालक एक महत्वपूर्ण व्यय हो सकता है। कम से कम यह उन परियोजनाओं पर है जो मैं हाल ही में कर रहा हूं। :-)
ब्रैंडन रोड्स

1

थ्रेड आधारित पूल में कोई निर्मित नहीं है। हालांकि, यह एक निर्माता / उपभोक्ता कतार को Queueवर्ग के साथ लागू करने के लिए बहुत जल्दी हो सकता है ।

इससे: 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
concurrent.futuresमॉड्यूल के साथ ऐसा नहीं है ।
थानाटोस

11
मुझे नहीं लगता कि यह अब बिल्कुल सच है। from multiprocessing.pool import ThreadPool
रान्डेल हंट


0

एक और तरीका इस प्रक्रिया को थ्रेडेड क्यू पूल में जोड़ सकता है

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)
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.