Пул потоков, похожий на пул многопроцессорных?

348

Существует ли класс 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, связанной с вводом-выводом, для которой оболочка Python выпустит GIL перед фактическим вызовом функции.

Должен ли я написать свой собственный пул потоков?

Мартин
источник
Вот что выглядит многообещающе в Поваренной книге Python: Рецепт 576519: Пул потоков с тем же API, что и (мульти) обработкой.
Pool
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
@mrgloom stackoverflow.com/questions/1294382
Дарклайтер

Ответы:

448

Я только что узнал , что на самом деле это поток-интерфейс Бассейн в multiprocessingмодуле, однако он скрыт несколько и не должным образом задокументированы.

Это может быть импортировано через

from multiprocessing.pool import ThreadPool

Он реализован с использованием фиктивного класса Process, обертывающего поток Python. Этот основанный на потоках класс Process можно найти, в multiprocessing.dummyкотором кратко упоминается в документации . Этот фиктивный модуль предположительно обеспечивает весь многопроцессорный интерфейс на основе потоков.

Мартин
источник
5
Это потрясающе. У меня возникла проблема с созданием ThreadPools вне основного потока, хотя вы можете использовать его из дочернего потока, созданного ранее. Я поставил вопрос: bugs.python.org/issue10015
Олсон,
82
Я не понимаю, почему у этого класса нет документации. Такие вспомогательные классы так важны в наше время.
Wernight
18
@Wernight: он не является общедоступным в первую очередь потому, что никто не предлагал патч, который предоставляет его (или что-то похожее) в качестве многопоточности. ThreadPool, включая документацию и тесты. Было бы неплохо включить батарею в стандартную библиотеку, но этого не произойдет, если никто не напишет ее. Одним приятным преимуществом этой существующей реализации в многопроцессорной обработке является то, что она должна значительно облегчить написание любого такого патча для потоков ( docs.python.org/devguide )
ncoghlan
3
@ daniel.gindi: multiprocessing.dummy.Pool/ multiprocessing.pool.ThreadPoolэто одно и то же, и оба являются пулами потоков. Они имитируют интерфейс пула процессов, но они полностью реализованы с точки зрения потоков. Перечитайте документы, вы получили это задом наперед.
ShadowRanger
9
@ daniel.gindi: Читайте дальше : « Копирует multiprocessing.dummyAPI, 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
чтобы использовать модуль backported futures, запустите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)
....
warfares
источник
9
Путь импорта для ThreadPoolотличается от Pool. Правильный импорт есть from multiprocessing.pool import ThreadPool.
Бархатцы
2
Странно, что это не документированный API, и multiprocessing.pool только кратко упоминается как предоставление AsyncResult. Но это доступно в 2.x и 3.x.
Марвин
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()

Для поддержки обратных вызовов при завершении задачи вы можете просто добавить обратный вызов в кортеж задачи.

dgorissen
источник
как могут потоки когда-либо присоединяться, если они безусловно бесконечный цикл?
Джозеф Гарвин
@JosephGarvin Я проверил это, и потоки продолжают блокировать пустую очередь (так как вызов Queue.get()блокируется) до завершения программы, после чего они автоматически завершаются.
форум
@JosephGarvin, хороший вопрос. Queue.join()фактически присоединится к очереди задач, а не к рабочим потокам. Таким образом, когда очередь пуста, wait_completionвозвращается, программа завершается, а потоки собираются в ОС.
рандомир
Если весь этот код обернут в аккуратную функцию, кажется, что он не останавливает потоки, даже когда очередь пуста и pool.wait_completion()возвращается. В результате потоки просто продолжают строить.
убиквибакон
17

Привет, чтобы использовать пул потоков в Python, вы можете использовать эту библиотеку:

from multiprocessing.dummy import Pool as ThreadPool

и затем для использования, эта библиотека делает так:

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

Потоки - это количество потоков, которые вы хотите, а задачи - это список задач, которые больше всего соответствуют сервису.

Маночер Расули
источник
Спасибо, это отличное предложение! Из документов: 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()
forumulator
источник
Анонс для других читателей: этот код - Python 3 (shebang #!/usr/bin/python3)
Даниэль Маршалл,
Почему вы используете, for i, d in enumerate(delays):а затем игнорировать iзначение?
Мартино
@martineau - вероятно, просто пережиток из разработки, где они, вероятно, хотели печатать iво время пробежки.
n1k31t4
Почему create_taskтам? Для чего это?
MrR
Я не могу поверить и ответить с 4 голосами на SO это способ сделать ThreadPooling в Python. Threadpool в официальном дистрибутиве python все еще не работает? Чего мне не хватает?
MrR
2

Затраты на создание новых процессов минимальны, особенно когда их всего 4. Я сомневаюсь, что это горячая точка производительности вашего приложения. Сохраняйте это простым, оптимизируйте, где вы должны и где указывают результаты профилирования.

unbeli
источник
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)
Pelos
источник