私はpythonのmultiprocessing
パッケージを初めて使用します。もっと知っている人にとっては、混乱しやすいでしょう。並行性について読んでいて、検索してきました。このような他の質問については、何も見つかりませんでした。(FYI I do [〜#〜] not [〜#〜] GILがアプリケーションの速度を低下させるため、multithreading
を使用したいたくさん。)
イベントの枠組みの中で考えています。イベントが発生するのを待って、複数のプロセスを実行したいと思います。イベントが発生すると、特定のプロセスに割り当てられ、プロセスは動作してからアイドル状態に戻ります。これを行うためのより良い方法があるかもしれませんが、私の理由は、イベントが発生するたびにプロセスを作成して閉じるのではなく、すべてのプロセスを一度スポーンして無期限に開いたままにする必要があるということです。速度は私にとって問題であり、私のイベントは1秒間に何千回も発生する可能性があります。
次のおもちゃの例を思いつきました。これは、あるプロセスに偶数を送信し、別のプロセスに奇数を送信することを目的としています。どちらのプロセスも同じで、リストに番号を追加するだけです。
from multiprocessing import Process, Queue, Pipe
slist=['even','odd']
Q={}
Q['even'] = Queue()
Q['odd'] = Queue()
ev,od = [],[]
Q['even'].put(ev)
Q['odd'].put(od)
P={}
P['even'] = Pipe()
P['odd'] = Pipe()
def add_num(s):
""" The worker function, invoked in a process. The results are placed in
a list that's pushed to a queue."""
# while True :
if not P[s][1].recv():
print s,'- do nothing'
else:
d = Q[s].get()
print d
d.append(P[s][1].recv())
Q[s].put(d)
print Q[s].get()
P[s][0].send(False)
print 'ya'
def piper(s,n):
P[s][0].send(n)
for k in [S for S in slist if S != s]:
P[k][0].send(False)
add_num(s)
procs = [ Process (
target=add_num,
args=(i,)
)
for i in ['even','odd']]
for s in slist:
P[s][0].send(False)
for p in procs:
p.start()
p.join()
for i in range(10):
print i
if i%2==0:
s = 'even'
else:
s = 'odd'
piper(s,i)
print 'results:', Q['odd'].get(),Q['even'].get()
このコードは次を生成します。
even - do nothing
私のコードや推論が不十分であるなど、この問題に対する賢明な洞察があれば大歓迎です。
これが私が数回使用して成功したアプローチです:
マルチプロセッシングプール を起動します。
マルチプロセッシング SyncManager を使用して、複数のキューを作成します(異なる方法で処理する必要があるデータのタイプごとに1つ)。
apply_async を使用して、データを処理する関数を起動します。キューと同様に、異なる方法で処理する必要があるデータのタイプごとに1つの関数が必要です。起動された各関数は、そのデータに対応するキューを入力引数として取得します。関数は、キューからデータを取得することから始まる無限ループで作業を行います。
処理を開始します。処理中に、メインプロセスはデータを並べ替え、どの関数がデータを処理するかを決定します。決定が下されると、データはその機能に対応するキューに配置されます。
すべてのデータが処理された後、メインプロセスは「ポイズンピル」と呼ばれる値を各キューに入れます。ポイズンピルは、作業者が処理するすべての人が終了の合図として認識する値です。キューは先入れ先出し(FIFO)であるため、キューの最後のアイテムとしてポイズンピルをプルすることが保証されています。
マルチプロセッシングプールを閉じて参加します。
以下は、このアルゴリズムの例です。サンプルコードの目標は、前述のアルゴリズムを使用して、奇数を2で除算し、偶数を-2で除算することです。すべての結果は、メインプロセスからアクセスできる共有リストに配置されます。
import multiprocessing
POISON_PILL = "STOP"
def process_odds(in_queue, shared_list):
while True:
# block until something is placed on the queue
new_value = in_queue.get()
# check to see if we just got the poison pill
if new_value == POISON_PILL:
break
# we didn't, so do the processing and put the result in the
# shared data structure
shared_list.append(new_value/2)
return
def process_evens(in_queue, shared_list):
while True:
new_value = in_queue.get()
if new_value == POISON_PILL:
break
shared_list.append(new_value/-2)
return
def main():
# create a manager - it lets us share native Python object types like
# lists and dictionaries without worrying about synchronization -
# the manager will take care of it
manager = multiprocessing.Manager()
# now using the manager, create our shared data structures
odd_queue = manager.Queue()
even_queue = manager.Queue()
shared_list = manager.list()
# lastly, create our pool of workers - this spawns the processes,
# but they don't start actually doing anything yet
pool = multiprocessing.Pool()
# now we'll assign two functions to the pool for them to run -
# one to handle even numbers, one to handle odd numbers
odd_result = pool.apply_async(process_odds, (odd_queue, shared_list))
even_result = pool.apply_async(process_evens, (even_queue, shared_list))
# this code doesn't do anything with the odd_result and even_result
# variables, but you have the flexibility to check exit codes
# and other such things if you want - see docs for AsyncResult objects
# now that the processes are running and waiting for their queues
# to have something, lets give them some work to do by iterating
# over our data, deciding who should process it, and putting it in
# their queue
for i in range(6):
if (i % 2) == 0: # use mod operator to see if "i" is even
even_queue.put(i)
else:
odd_queue.put(i)
# now we've finished giving the processes their work, so send the
# poison pill to tell them to exit
even_queue.put(POISON_PILL)
odd_queue.put(POISON_PILL)
# wait for them to exit
pool.close()
pool.join()
# now we can check the results
print(shared_list)
# ...and exit!
return
if __name__ == "__main__":
main()
このコードは次の出力を生成します:
[0.5、-0.0、1.5、-1.0、2.5、-2.0]
関数がキューからアイテムを取得して結果をリストに入れることができる順序を保証できないため、結果の順序は予測できないことに注意してください。ただし、並べ替えなど、必要な後処理を行うことはできます。
これはあなたの問題に対する良い解決策になると思います:
スポーンプロセスには大きなオーバーヘッドがあるのは正しいです。この単一のプロデューサー/複数のコンシューマーのアプローチにより、プールを使用してプログラムの全期間にわたってワーカーを存続させることができなくなります。
これは、データの属性に応じて異なる方法でデータを処理できることに関する懸念に対処します。コメントの中で、特定のプロセスにデータを送信できることについて懸念を表明しました。このアプローチでは、データを配置するキューを選択する必要があるため、データを提供するプロセスを選択できます。 (ちなみに、あなたは pool.map 関数を考えていると思います。これは、あなたが正しく信じているように、同じジョブで異なる操作を実行することを許可しません。apply_async
します。)
私はそれが非常に拡張可能で柔軟性があることを発見しました。より多くの種類のデータ処理を追加する必要がありますか?ハンドラー関数を記述し、キューをもう1つ追加し、ロジックをmainに追加して、データを新しい関数にルーティングするだけです。 1つのキューがバックアップされ、ボトルネックになっていることに気づいていますか?同じターゲット関数でapply_async
を呼び出し、複数回キューに入れて、複数のワーカーを同じキューで作業させることができます。すべての労働者が1つを取得できるように、キューに十分な毒薬を与えるようにしてください。
キューに渡すデータはすべて、pickleモジュールによってpickle化可能(シリアル化可能)である必要があります。 ここ を見て、酸洗いできるものとできないものを確認してください。
おそらく他の制限もあるでしょうが、頭のてっぺんから他のことを考えることはできません。