เธรดพูลคล้ายกับการประมวลผลหลายพูล?


347

มีคลาส Pool สำหรับเธรดผู้ปฏิบัติงานซึ่งคล้ายกับคลาส Poolของโมดูลมัลติโพรเซสเซอร์หรือไม่?

ฉันชอบตัวอย่างวิธีที่ง่ายที่สุดในการทำให้ขนานกับฟังก์ชันแผนที่

def long_running_func(p):
    c_func_no_gil(p)

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

อย่างไรก็ตามฉันต้องการทำโดยไม่มีค่าใช้จ่ายในการสร้างกระบวนการใหม่

ฉันรู้เกี่ยวกับ GIL อย่างไรก็ตามใน usecase ของฉันฟังก์ชั่นจะเป็นฟังก์ชั่น C แบบ IO-bound ซึ่งตัวห่องูหลามจะปล่อย GIL ก่อนการเรียกใช้ฟังก์ชันจริง

ฉันต้องเขียนเธรดพูลของตัวเองหรือไม่?


นี่คือสิ่งที่ดูเหมือนจะเป็นสัญญาในตำรา Python: สูตร 576519: กลุ่มเธรดที่มี API เดียวกับการประมวลผลหลายรายการพูล (Python)
อื่น ๆ

1
ทุกวันนี้เป็นแบบในตัว: from multiprocessing.pool import ThreadPool.
martineau

คุณสามารถทำอย่างละเอียดเกี่ยวกับเรื่องนี้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.?
mrgloom

คำตอบ:


448

ฉันเพิ่งค้นพบว่ามีจริง เป็นอินเตอร์เฟซที่สระว่ายน้ำด้ายอยู่ในmultiprocessingโมดูล แต่มันถูกซ่อนอยู่บ้างและเอกสารไม่ถูกต้อง

มันสามารถนำเข้าผ่าน

from multiprocessing.pool import ThreadPool

มันถูกนำมาใช้โดยใช้กระบวนการจำลองชั้นห่อด้ายหลาม กระบวนการคลาด้ายตามนี้สามารถพบได้ในmultiprocessing.dummyซึ่งเป็นเวลาสั้น ๆ ที่กล่าวถึงในเอกสาร โมดูลตัวจำลองนี้ควรจัดหาอินเตอร์เฟสแบบหลายตัวประมวลผลทั้งหมดตามเธรด


5
ที่น่ากลัว. ฉันมีปัญหาในการสร้าง ThreadPools นอกเธรดหลักคุณสามารถใช้พวกเขาจากเธรดเด็กสร้างครั้งเดียวแม้ว่า ฉันใส่ปัญหาลงไปในนั้น: bugs.python.org/issue10015
Olson

82
ฉันไม่เข้าใจว่าทำไมชั้นนี้ไม่มีเอกสารประกอบ ชั้นเรียนผู้ช่วยดังกล่าวมีความสำคัญมากในปัจจุบัน
Wernight

18
@ ความสว่าง: มันไม่เปิดเผยต่อสาธารณชนเป็นหลักเพราะไม่มีใครเสนอแพทช์ที่ให้ (หรือบางอย่างที่คล้ายกัน) เป็นเธรด ThreadPool รวมถึงเอกสารและการทดสอบ แน่นอนว่ามันจะเป็นแบตเตอรี่ที่ดีที่จะรวมไว้ในไลบรารีมาตรฐาน แต่จะไม่เกิดขึ้นหากไม่มีใครเขียนมัน ข้อดีอย่างหนึ่งของการใช้งานที่มีอยู่ในการประมวลผลแบบมัลติโพรเซสคือมันควรทำให้แพตช์การเธรดดังกล่าวง่ายขึ้นมากในการเขียน ( docs.python.org/devguide )
ncoghlan

3
@ daniel.gindi: multiprocessing.dummy.Pool/ multiprocessing.pool.ThreadPoolเป็นสิ่งเดียวกันและเป็นทั้งกลุ่มเธรด พวกเขาเลียนแบบอินเทอร์เฟซของกลุ่มกระบวนการ แต่พวกเขาจะดำเนินการทั้งหมดในแง่ของเธรด อ่านเอกสารอีกครั้งคุณเข้าใจมันย้อนหลัง
ShadowRanger

9
@ daniel.gindi: อ่านเพิ่มเติม : " multiprocessing.dummyจำลอง API multiprocessingแต่ไม่เกิน wrapper รอบ ๆthreadingโมดูล" multiprocessingโดยทั่วไปเป็นเรื่องเกี่ยวกับกระบวนการ แต่เพื่อให้สามารถสลับไปมาระหว่างกระบวนการและเธรดพวกเขา (ส่วนใหญ่) ทำซ้ำmultiprocessingAPI ในmultiprocessing.dummyแต่ได้รับการสนับสนุนด้วยเธรดไม่ใช่กระบวนการ เป้าหมายคือช่วยให้คุณสามารถimport multiprocessing.dummy as multiprocessingเปลี่ยนรหัสที่ใช้ในกระบวนการเป็นเธรดได้
ShadowRanger

236

ใน Python 3 คุณสามารถใช้concurrent.futures.ThreadPoolExecutorเช่น:

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

ดูเอกสารสำหรับข้อมูลเพิ่มเติมและตัวอย่าง


6
เพื่อที่จะใช้โมดูลฟิวเจอร์ส backported ให้รันsudo pip install futures
yair

มันเป็นวิธีที่มีประสิทธิภาพและรวดเร็วที่สุดสำหรับการประมวลผลแบบมัลติ
Haritsinh Gohil

2
ความแตกต่างระหว่างการใช้ThreadPoolExecutorและmultiprocessing.dummy.Poolคืออะไร?
Jay

63

ใช่และดูเหมือนว่าจะมี API เดียวกัน (มากหรือน้อย)

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
น่าแปลกที่นี่ไม่ใช่ API ที่ทำเป็นเอกสารและ multiprocessing.pool จะกล่าวถึงสั้น ๆ ว่า AsyncResult แต่มันมีอยู่ใน 2.x และ 3.x
Marvin

2
นี่คือสิ่งที่ฉันกำลังมองหา มันเป็นเพียงการนำเข้าบรรทัดเดียวและการเปลี่ยนแปลงเพียงเล็กน้อยในสายพูลที่มีอยู่ของฉันและทำงานได้อย่างสมบูรณ์
Danegraphics

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

เพื่อรองรับการโทรกลับเมื่องานเสร็จสมบูรณ์คุณสามารถเพิ่มการโทรกลับไปที่ทูเปิลภารกิจ


เธรดสามารถเข้าร่วมได้อย่างไรหากไม่มีการวนซ้ำแบบไม่มีเงื่อนไข
Joseph Garvin

@JosephGarvin ฉันได้ทำการทดสอบแล้วและเธรดยังคงบล็อกในคิวที่ว่างเปล่า (ตั้งแต่การเรียกไปสู่Queue.get()การบล็อก) จนกว่าโปรแกรมจะสิ้นสุดลงหลังจากนั้นพวกเขาจะถูกยกเลิกโดยอัตโนมัติ
forumulator

@JosephGarvin เป็นคำถามที่ดี Queue.join()จะเข้าร่วมคิวงานจริง ๆไม่ใช่เธรดผู้ปฏิบัติงาน ดังนั้นเมื่อคิวว่างเปล่าwait_completionผลตอบแทนจะสิ้นสุดลงโปรแกรมและเธรดจะถูกอ่านโดยระบบปฏิบัติการ
Randomir

หากโค้ดทั้งหมดนี้ถูกรวมเข้ากับฟังก์ชั่นเรียบร้อยมันจะไม่หยุดเธรดแม้ว่าคิวจะว่างเปล่าและpool.wait_completion()ส่งคืน ผลลัพธ์คือเธรดเพิ่งสร้าง
ubiquibacon

17

สวัสดีที่จะใช้เธรดพูลใน Python คุณสามารถใช้ไลบรารี่นี้:

from multiprocessing.dummy import Pool as ThreadPool

แล้วสำหรับการใช้งานห้องสมุดนี้ทำเช่นนั้น:

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

เธรดคือจำนวนเธรดที่คุณต้องการและงานคือรายการของงานที่แม็พกับบริการมากที่สุด


ขอบคุณนั่นเป็นคำแนะนำที่ยอดเยี่ยม! จาก docs: multiprocessing.dummy จำลอง API ของการประมวลผลหลายตัว แต่ไม่เกิน wrapper รอบ ๆ โมดูลเธรด One การแก้ไข - ฉันคิดว่าคุณต้องการที่จะบอกว่าสระว่ายน้ำ API คือ (ฟังก์ชั่น iterable)
layser

2
เราพลาด.close()และ.join()การโทรและสาเหตุที่.map()จะเสร็จสิ้นก่อนหัวข้อทั้งหมดที่ดำเนินการเสร็จแล้ว เพียงแค่คำเตือน
Anatoly Scherbakov

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

หมายเหตุสำหรับผู้อ่านอื่น ๆ : รหัสนี้คือ Python 3 (shebang #!/usr/bin/python3)
Daniel Marschall

เหตุใดคุณจึงใช้for i, d in enumerate(delays):และไม่สนใจiค่า
martineau

@martineau - อาจเป็นเพียงของที่ระลึกจากการพัฒนาที่พวกเขาอาจต้องการพิมพ์iในระหว่างการวิ่ง
n1k31t4

ทำไมถึงcreate_taskมี มีไว้เพื่ออะไร?
MrR

ฉันไม่สามารถเชื่อและตอบด้วย 4 คะแนนโหวตบน SO เป็นวิธีการ ThreadPooling ใน Python Threadpool ในการแจกแจงไพ ธ อนอย่างเป็นทางการยังคงใช้งานไม่ได้? ฉันพลาดอะไรไป
MrR

2

ค่าใช้จ่ายในการสร้างกระบวนการใหม่นั้นน้อยมากโดยเฉพาะเมื่อเป็นเพียงแค่ 4 กระบวนการเท่านั้น ฉันสงสัยว่านี่คือแอปพลิเคชั่นยอดนิยมของคุณ ทำให้ง่ายเพิ่มประสิทธิภาพในที่ที่คุณต้องไปและที่ซึ่งผลการทำโปรไฟล์ชี้ไปที่


5
หากผู้ถามอยู่ภายใต้ Windows (ซึ่งฉันไม่เชื่อว่าเขาระบุไว้) จากนั้นฉันคิดว่ากระบวนการ spinup อาจเป็นค่าใช้จ่ายจำนวนมาก อย่างน้อยก็อยู่ในโครงการที่ฉันเพิ่งทำไป :-)
Brandon Rhodes

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โมดูลอีกต่อไป
Thanatos

11
ฉันไม่คิดว่ามันจะเป็นจริงอีกต่อไปแล้ว from multiprocessing.pool import ThreadPool
Randall Hunt


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)
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.