マルチプロセッシングプールと同様のスレッドプール?


347

マルチプロセッシングモジュールのプールクラスと同様に、ワーカースレッド用のプールクラスはありますか?

たとえば、マップ関数を並列化する簡単な方法が好きです

def long_running_func(p):
    c_func_no_gil(p)

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

ただし、新しいプロセスを作成するオーバーヘッドなしでそれを実行したいと思います。

GILについて知っています。ただし、私のユースケースでは、関数はIOにバインドされたC関数になり、Pythonラッパーは実際の関数呼び出しの前にGILを解放します。

独自のスレッドプールを作成する必要がありますか?


これは、Pythonクックブックで有望に見えるものです。レシピ576519:(multi)processing.Pool(Python)と同じAPIを持つスレッドプール
otherchirps

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

PythonスレッドをラップするダミーのProcessクラスを使用して実装されます。このスレッドベースのProcessクラスはmultiprocessing.dummyドキュメントで簡単に説明されています。このダミーモジュールは、スレッドに基づいたマルチプロセッシングインターフェイス全体を提供していると思われます。


5
それは素晴らしいです。メインスレッドの外でThreadPoolを作成するときに問題が発生しましたが、一度作成した子スレッドから使​​用できます。私はそれに問題を入れました:bugs.python.org/issue10015
Olson

82
このクラスにドキュメントがない理由はわかりません。このようなヘルパークラスは、今日非常に重要です。
2012年

18
@Wernight:ドキュメンテーションとテストを含めて、それをthreading.ThreadPoolとして提供するパッチ(または類似のもの)を提供したパッチを誰も提供していないため、これは主に公開されていません。これは確かに標準ライブラリに含めるのに適したバッテリーですが、誰も書いていない場合は起こりません。マルチプロセッシングでは、この既存の実装の一つの素敵な利点は、それがどのようにスレッド化パッチを作成する必要があることにある多くの書き込み(に簡単にdocs.python.org/devguide
ncoghlan

3
@ daniel.gindi:multiprocessing.dummy.Pool/ multiprocessing.pool.ThreadPoolは同じものであり、両方ともスレッドプールです。これらはプロセスプールのインターフェイスを模倣しますが、スレッド化に関して完全に実装されています。ドキュメントをもう一度読んでください。
ShadowRanger

9
@ daniel.gindi:さらに読む:「multiprocessing.dummyAPIを複製しますがmultiprocessingthreadingモジュールのラッパーにすぎません。」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

それはマルチプロセッシングの最も効率的で最速の方法です
Haritsinh Gohil '8/08/19

2
使用しての違いは何であるThreadPoolExecutorとはmultiprocessing.dummy.Pool
ジェイ

2
concurrent.futuresからインポートThreadPoolExecutor
stackOverlord

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 2018

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戻り、プログラムが終了し、OSによってスレッドが取得されます。
ランダミール2018

このコードのすべてがきちんとした関数にまとめられている場合、キューが空で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

スレッドは必要なスレッドの数であり、タスクはサービスに最もマッピングされるタスクのリストです。


ありがとう、それは素晴らしい提案です!ドキュメントから:multiprocessing.dummyはマルチプロセッシングのAPIを複製しますが、スレッディングモジュールのラッパーにすぎません。1つの修正-プールAPIは(関数、反復可能)であると言いたいと思います
レイヤー

2
.close()との.join()呼び出しを逃したため、.map()すべてのスレッドが終了する前に終了します。ただの警告です。
アナトリーシェルバコフ2018

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-おそらく、彼らiが実行中に印刷したかった開発からの遺物。
n1k31t4 2018年

なぜcreate_taskあるのですか?それはなんのためですか?
MrR

私は4票でSOを信じて答えることはできません。PythonでThreadPoolingを行う方法です。公式のPythonディストリビューションのスレッドプールはまだ壊れていますか?何が欠けていますか?
MrR

2

新しいプロセスを作成するオーバーヘッドは、特にプロセスが4つだけの場合は最小限です。これはアプリケーションのパフォーマンスホットスポットではないかと思います。シンプルに保ち、必要な場所とプロファイリング結果が示す場所を最適化します。


5
質問者がWindowsを使用している場合(彼が指定したとは思わない)、プロセスのスピンアップはかなりの費用になる可能性があると思います。少なくとも、私が最近行っているプロジェクトについてです。:-)
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モジュールには当てはまりません。
タナトス2014

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.