Нитка пулу схожа на багатопроцесорний пул?


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. Однак у моєму футлярі функція буде функцією C, пов'язаної з IO, для якої питонний обгортник звільнить GIL перед фактичним викликом функції.

Чи потрібно писати власний пул для нарізки?


Ось те, що виглядає багатообіцяючим у кулінарній книзі Python: Рецепт 576519: Пул потоків з тим самим API, що і (багато) обробка.
Пул

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.?
mrgloom

Відповіді:


448

Я тільки що дізнався , що на насправді це потік-інтерфейс Басейн в multiprocessingмодулі, проте він прихований кілька і не належним чином задокументовані.

Її можна імпортувати через

from multiprocessing.pool import ThreadPool

Він реалізований за допомогою фіктивного класу Process, який обгортає нитку python. Цей клас процесів на основі потоку можна знайти, в multiprocessing.dummyякому коротко згадується в документах . Цей фіктивний модуль нібито забезпечує весь багатопроцесорний інтерфейс на основі потоків.


5
Це круто. У мене виникла проблема зі створенням ThreadPools за межами основної теми, але ви можете використовувати їх із дочірнього потоку, щойно створеного. Я поставив для цього проблему: bugs.python.org/issue10015
Олсон

82
Я не розумію, чому цей клас не має документації. Такі заняття-помічники сьогодні так важливі.
Wernight

18
@Wernight: вона не є загальнодоступною, оскільки ніхто не запропонував патч, який надає її (або щось подібне), як нарізка. Дійсно було б включити батарею до стандартної бібліотеки, але це не станеться, якщо ніхто її не напише. Однією з приємних переваг цієї існуючої реалізації в мультипроцесорі є те, що вона повинна зробити будь-який такий патч для нарізки набагато простішим записом ( docs.python.org/devguide )
ncoghlan

3
@ daniel.gindi: multiprocessing.dummy.Pool/ multiprocessing.pool.ThreadPool- це одне і те ж і обидва пули потоків. Вони імітують інтерфейс процесорного пулу, але цілком реалізовані з точки зору нарізки. Перечитайте документи, ви отримали їх назад.
ShadowRanger

9
@ daniel.gindi: Читайте далі : " multiprocessing.dummyкопіює API, multiprocessingале не більше, ніж обгортку навколо 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
щоб використовувати підтримуваний модуль ф'ючерсів, запустітьsudo pip install futures
yair

це найефективніший і найшвидший спосіб багаторазової обробки
Харіцин Гохіль

2
Яка різниця між використанням ThreadPoolExecutorта multiprocessing.dummy.Pool?
Джей

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.
Марвін

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()блокується) до завершення програми, після чого вони автоматично припиняються.
форумчатор

@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 багатопроцесорної обробки, але є не більше ніж обгорткою навколо модуля нарізки. Одне виправлення - я думаю, ви хочете сказати, що api у пулі є (функція, ітерабельна)
layser

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

Анотація для інших читачів: Цей код Python 3 (shebang #!/usr/bin/python3)
Даніель Маршалл

Чому ви використовуєте, for i, d in enumerate(delays):а потім ігноруєте iзначення?
мартіно

@martineau - ймовірно, просто реліквія з розробки, де вони, ймовірно, хотіли надрукувати iпід час пробігу.
n1k31t4

Чому create_taskтам? Для чого це?
MrR

Я не можу повірити і відповісти 4 голосами на ТА - це спосіб зробити ThreadPooling в Python. Нитка в офіційному розподілі пітонів все ще зламана? Що я пропускаю?
MrR

2

Витрати на створення нових процесів мінімальні, особливо якщо їх всього 4. Я сумніваюся, що це гаряча точка вашої заявки. Нехай це буде просто, оптимізуйте, де вам потрібно і де вказують результати профілювання.


5
Якщо запитувач знаходиться під Windows (що я не вважаю, що він вказав), то я вважаю, що прокрутка процесу може бути значною витратою. Принаймні, це стосується тих проектів, якими я останнім часом займаюся. :-)
Брендон Родос

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 та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.