マルチプロセッシングモジュールの プールクラス のように、ワーカースレッド用のプールクラスはありますか?
私は例えば地図関数を並列化する簡単な方法が好きです
def long_running_func(p):
c_func_no_gil(p)
p = multiprocessing.Pool(4)
xs = p.map(long_running_func, range(100))
しかし、私は新しいプロセスを作成するというオーバーヘッドなしでそれをやりたいと思います。
私はGILについて知っています。しかし、私のユースケースでは、この関数は、実際の関数呼び出しの前にPythonラッパーがGILを解放するIOバインドC関数になります。
独自のスレッドプールを作成する必要がありますか?
私は、実際にがmultiprocessing
モジュールの中にスレッドベースのプールインターフェースであることを知りましたが、いくらか隠されていて、適切に文書化されていません。
それはを介してインポートすることができます
from multiprocessing.pool import ThreadPool
PythonスレッドをラップするダミーのProcessクラスを使用して実装されています。このスレッドベースのProcessクラスは multiprocessing.dummy
で見つけることができます。これは のドキュメントで簡単に述べられています 。このダミーモジュールは、おそらくスレッドに基づいてマルチプロセッシングインターフェイス全体を提供します。
Python 3では、 concurrent.futures.ThreadPoolExecutor
を使うことができます。
executor = ThreadPoolExecutor(max_workers=10)
a = executor.submit(my_function)
詳細と例については、 のドキュメント を参照してください。
はい、そして(ほぼ)同じ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)
....
非常に単純で軽量なもの(ここで からわずかに変更されたもの ):
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 __== '__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()
タスク完了時のコールバックをサポートするには、コールバックをタスクTupleに追加するだけです。
こんにちはPythonでスレッドプールを使用するには、このライブラリを使用することができます:
from multiprocessing.dummy import Pool as ThreadPool
そして使用するために、このライブラリはそのようにします:
pool = ThreadPool(threads)
results = pool.map(service, tasks)
pool.close()
pool.join()
return results
スレッドは必要なスレッドの数であり、タスクは最もサービスにマップされているタスクのリストです。
これが私がついに使用した結果です。これは上記の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()
新しいプロセスを作成することによるオーバーヘッドは、特にそれがそれらのうちの4つだけである場合、最小限になります。私はこれがあなたのアプリケーションのパフォーマンスのホットスポットではないかと思います。それを単純にして、あなたがしなければならないところとプロファイリング結果が指すところを最適化しなさい。
スレッドベースのプールは組み込まれていません。ただし、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