Nhóm luồng tương tự như nhóm đa xử lý?


347

Có một lớp Pool cho các luồng công nhân , tương tự như lớp Pool của mô đun đa xử lý không?

Tôi thích ví dụ cách dễ dàng để song song hóa một chức năng bản đồ

def long_running_func(p):
    c_func_no_gil(p)

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

tuy nhiên tôi muốn làm điều đó mà không cần chi phí tạo ra các quy trình mới.

Tôi biết về GIL. Tuy nhiên, trong usecase của tôi, hàm sẽ là hàm C ràng buộc IO mà trình bao bọc python sẽ giải phóng GIL trước khi gọi hàm thực tế.

Tôi có phải viết nhóm luồng của riêng tôi?


Đây là một cái gì đó có vẻ hứa hẹn trong Python Cookbook: Recipe 576519: Nhóm luồng có cùng API với (multi)
process.Pool

1
Ngày nay, nó được tích hợp : from multiprocessing.pool import ThreadPool.
martineau

Bạn có thể giải thích về điều này 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

Câu trả lời:


448

Tôi chỉ phát hiện ra rằng thực sự một giao diện Pool dựa trên luồng trong multiprocessingmô-đun, tuy nhiên nó bị ẩn đi đôi chút và không được ghi lại đúng cách.

Nó có thể được nhập qua

from multiprocessing.pool import ThreadPool

Nó được thực hiện bằng cách sử dụng một lớp Process giả bao bọc một chuỗi python. Lớp Process dựa trên luồng này có thể được tìm thấy trong multiprocessing.dummyđó được đề cập ngắn gọn trong các tài liệu . Mô-đun giả này được cho là cung cấp toàn bộ giao diện đa xử lý dựa trên các luồng.


5
Thật tuyệt vời. Tôi gặp vấn đề khi tạo ThreadPool bên ngoài luồng chính, bạn có thể sử dụng chúng từ luồng con sau khi đã tạo. Tôi đặt một vấn đề cho nó: bug.python.org/su10015
Olson

82
Tôi không hiểu tại sao lớp này không có tài liệu. Các lớp người trợ giúp như vậy là rất quan trọng ngày nay.
Wernight

18
@Wernight: nó không công khai chủ yếu vì không ai cung cấp bản vá cung cấp nó (hoặc một cái gì đó tương tự) như luồng .ThreadPool, bao gồm tài liệu và bài kiểm tra. Nó thực sự sẽ là một pin tốt để đưa vào thư viện tiêu chuẩn, nhưng nó sẽ không xảy ra nếu không ai viết nó. Một lợi thế thoải mái thực hiện hiện này trong đa xử, là nó phải thực hiện bất kỳ bản vá luồng như nhiều dễ dàng hơn để viết ( docs.python.org/devguide )
ncoghlan

3
@ daniel.gindi: multiprocessing.dummy.Pool/ multiprocessing.pool.ThreadPoollà cùng một thứ, và là cả hai nhóm chủ đề. Chúng bắt chước giao diện của một nhóm quy trình, nhưng chúng được thực hiện hoàn toàn về mặt luồng. Đọc lại các tài liệu, bạn đã nhận được nó ngược.
ShadowRanger

9
@ daniel.gindi: Đọc thêm : " multiprocessing.dummysao chép API của multiprocessingnhưng không nhiều hơn một trình bao bọc xung quanh threadingmô-đun." multiprocessingnói chung là về các quy trình, nhưng để cho phép chuyển đổi giữa các quy trình và luồng, chúng (hầu hết) đã sao chép multiprocessingAPI trong multiprocessing.dummy, nhưng được hỗ trợ bằng các luồng, không phải các quy trình. Mục tiêu là cho phép bạn thực hiện import multiprocessing.dummy as multiprocessingđể thay đổi mã dựa trên quy trình thành dựa trên luồng.
ShadowRanger

236

Trong Python 3 bạn có thể sử dụng concurrent.futures.ThreadPoolExecutor, tức là:

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

Xem các tài liệu để biết thêm thông tin và ví dụ.


6
để sử dụng mô-đun tương lai được nhập, hãy chạysudo pip install futures
yair

đó là cách hiệu quả nhất và nhanh nhất để xử lý đa năng
Haritsinh Gohil

2
Sự khác biệt giữa sử dụng ThreadPoolExecutorvà là multiprocessing.dummy.Poolgì?
Jay

2
từ đồng
thời.futures

63

Có, và dường như có (ít nhiều) cùng một 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
Đường dẫn nhập cho ThreadPoolkhác với Pool. Nhập đúng là from multiprocessing.pool import ThreadPool.
Cúc vạn thọ

2
Kỳ lạ thay, đây không phải là một API tài liệu và multirocessing.pool chỉ được đề cập ngắn gọn là cung cấp AsyncResult. Nhưng nó có sẵn trong 2.x và 3.x.
Marvin

2
Đây là những gì tôi đang tìm kiếm. Nó chỉ là một dòng nhập khẩu duy nhất và một thay đổi nhỏ cho dòng pool hiện tại của tôi và nó hoạt động hoàn hảo.
Danegraphics

39

Đối với một cái gì đó rất đơn giản và nhẹ (sửa đổi một chút từ đây ):

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

Để hỗ trợ các cuộc gọi lại khi hoàn thành nhiệm vụ, bạn chỉ cần thêm cuộc gọi lại vào bộ tác vụ.


Làm thế nào các chủ đề có thể tham gia nếu vòng lặp vô hạn vô điều kiện?
Joseph Garvin

@JosephGarvin Tôi đã kiểm tra nó và các luồng tiếp tục chặn trên một hàng đợi trống (vì cuộc gọi đến Queue.get()đang bị chặn) cho đến khi chương trình kết thúc, sau đó chúng sẽ tự động chấm dứt.
đàn

@JosephGarvin, câu hỏi hay. Queue.join()sẽ thực sự tham gia hàng đợi nhiệm vụ, không phải chủ đề công nhân. Vì vậy, khi hàng đợi trống, wait_completiontrả về, kết thúc chương trình và các luồng được hệ điều hành gặt hái.
Randomir

Nếu tất cả các mã này được gói gọn trong một chức năng gọn gàng, nó dường như không dừng các luồng ngay cả khi hàng đợi trống và pool.wait_completion()trả về. Kết quả là chủ đề chỉ tiếp tục xây dựng.
ubiquibacon

17

Xin chào để sử dụng nhóm luồng trong Python, bạn có thể sử dụng thư viện này:

from multiprocessing.dummy import Pool as ThreadPool

và sau đó để sử dụng, thư viện này làm như thế:

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

Các luồng là số lượng luồng mà bạn muốn và các tác vụ là một danh sách các tác vụ mà hầu hết ánh xạ tới dịch vụ.


Cảm ơn, đó là một gợi ý tuyệt vời! Từ các tài liệu: Multiprocessing.dummy sao chép API của đa xử lý nhưng không nhiều hơn một trình bao bọc xung quanh mô-đun luồng. Một điều chỉnh - Tôi nghĩ rằng bạn muốn nói rằng api pool là (chức năng, có thể lặp lại)
layser

2
Chúng tôi đã bỏ lỡ .close().join()các cuộc gọi và đó là nguyên nhân .map()để kết thúc trước khi tất cả các chủ đề kết thúc. Chỉ là một cảnh báo.
Anatoly Scherbakov

8

Đây là kết quả cuối cùng tôi đã sử dụng. Đây là phiên bản sửa đổi của các lớp bởi dgorissen ở trên.

Tập tin: 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)

Sử dụng hồ bơi

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

Gây khó chịu cho những độc giả khác: Mã này là Python 3 (shebang #!/usr/bin/python3)
Daniel Marschall

Tại sao bạn sử dụng for i, d in enumerate(delays):và sau đó bỏ qua igiá trị?
martineau

@martineau - có lẽ chỉ là một di tích từ sự phát triển nơi họ có thể muốn in itrong quá trình chạy.
n1k31t4

Tại sao create_taskcó? Nó dùng để làm gì?
MrR

Tôi không thể tin và trả lời với 4 phiếu trên SO là cách thực hiện ThreadPooling trong Python. Threadpool trong bản phân phối python chính thức vẫn bị hỏng? Tôi đang thiếu gì?
MrR

2

Chi phí chung của việc tạo các quy trình mới là tối thiểu, đặc biệt là khi chỉ có 4 quy trình. Tôi nghi ngờ đây là một điểm nóng hiệu suất của ứng dụng của bạn. Giữ cho nó đơn giản, tối ưu hóa nơi bạn phải đến và nơi kết quả hồ sơ chỉ đến.


5
Nếu người hỏi ở dưới Windows (mà tôi không tin là anh ta đã chỉ định), thì tôi nghĩ rằng quá trình spinup có thể là một chi phí đáng kể. Ít nhất đó là trên các dự án mà tôi đã làm gần đây. :-)
Brandon Rhodes

1

Không có xây dựng trong hồ bơi dựa trên chủ đề. Tuy nhiên, có thể rất nhanh để thực hiện hàng đợi nhà sản xuất / người tiêu dùng vớiQueue lớp.

Từ: https://docs.python.org/2/l 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
Đây không còn là trường hợp với các concurrent.futuresmô-đun.
Thanatos

11
Tôi không nghĩ điều này đúng nữa. from multiprocessing.pool import ThreadPool
Randall Hunt


0

một cách khác có thể là thêm quá trình vào nhóm hàng đợi

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)
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.