web-dev-qa-db-ja.com

python multiprocessing poolでワーカーの一意のIDを取得します

pythonマルチプロセッシングプールの各ワーカーに一意のIDを割り当てる方法はあります。プール内の特定のワーカーによって実行されているジョブが、どのワーカーを実行しているかを知ることができますか?ドキュメント、Processにはnameがありますが、

名前は、識別目的でのみ使用される文字列です。セマンティクスはありません。複数のプロセスに同じ名前を付けることができます。

特定のユースケースでは、4つのGPUのグループで多数のジョブを実行し、ジョブを実行するGPUのデバイス番号を設定する必要があります。ジョブの長さは不均一であるため、前のジョブが完了する前に実行しようとしているジョブのGPUで衝突が発生しないようにしたい(したがって、IDを事前に割り当てることはできません事前に作業単位)。

36
JoshAdel

multiprocessing.current_process()という単純なもののようです。例えば:

_import multiprocessing

def f(x):
    print multiprocessing.current_process()
    return x * x

p = multiprocessing.Pool()
print p.map(f, range(6))
_

出力:

_$ python foo.py 
<Process(PoolWorker-1, started daemon)>
<Process(PoolWorker-2, started daemon)>
<Process(PoolWorker-3, started daemon)>
<Process(PoolWorker-1, started daemon)>
<Process(PoolWorker-2, started daemon)>
<Process(PoolWorker-4, started daemon)>
[0, 1, 4, 9, 16, 25]
_

これにより、プロセスオブジェクト自体が返されるため、プロセスは独自のIDになります。一意の数値IDに対してidを呼び出すこともできます-cpythonでは、これはプロセスオブジェクトのメモリアドレスなので、私はしませんthink重複する可能性があります。最後に、プロセスのidentまたはpidプロパティを使用できますが、これはプロセスが開始された後にのみ設定されます。

さらに、ソースを見ると、自動生成された名前(上記のProcess repr文字列の最初の値で例示されている)は一意である可能性が非常に高いようです。 multiprocessingは、プロセスごとに_itertools.counter_オブジェクトを保持します。これは、生成されるすべての子プロセスの __identity_ タプルを生成するために使用されます。したがって、最上位プロセスは、単一値IDを持つ子プロセスを生成し、2値IDを持つプロセスを生成します。次に、Processコンストラクターに名前が渡されない場合は、':'.join(...)を使用して、_identityに基づいて単純に 名前を自動生成 します。次に、Pool名前を変更 を使用してプロセスのreplaceを使用し、自動生成されたIDは同じままにします。

これらすべての結果は、2つのProcesses mayは同じ名前ですが、それらを作成するときにmayに同じ名前を割り当てるためです。 nameパラメーターに触れない場合は一意です。また、理論的には一意の識別子として__identity_を使用できます。しかし、私は彼らが理由でその変数をプライベートにしたと集めています!

動作中の上記の例:

_import multiprocessing

def f(x):
    created = multiprocessing.Process()
    current = multiprocessing.current_process()
    print 'running:', current.name, current._identity
    print 'created:', created.name, created._identity
    return x * x

p = multiprocessing.Pool()
print p.map(f, range(6))
_

出力:

_$ python foo.py 
running: PoolWorker-1 (1,)
created: Process-1:1 (1, 1)
running: PoolWorker-2 (2,)
created: Process-2:1 (2, 1)
running: PoolWorker-3 (3,)
created: Process-3:1 (3, 1)
running: PoolWorker-1 (1,)
created: Process-1:2 (1, 2)
running: PoolWorker-2 (2,)
created: Process-2:2 (2, 2)
running: PoolWorker-4 (4,)
created: Process-4:1 (4, 1)
[0, 1, 4, 9, 16, 25]
_
62
senderle

_multiprocessing.Queue_を使用してIDを保存し、プールプロセスの初期化時にIDを取得できます。

利点:

  • 内部に依存する必要はありません。
  • 使用事例がリソース/デバイスの管理である場合、デバイス番号を直接入力できます。これにより、デバイスが2回使用されないことも保証されます。プール内にデバイスよりも多くのプロセスがある場合、追加のプロセスはqueue.get()でブロックし、作業を実行しません(これはポーグラムをブロックしませんが、または少なくとも私がテストしたときはそうではなかった)。

短所:

  • 追加の通信オーバーヘッドがあり、プールプロセスの生成には少し時間がかかります。例のsleep(1)がなければ、他のプロセスはまだ初期化されていないため、最初のプロセスですべての作業が実行されます。
  • グローバルが必要です(または、少なくとも私はそれを回避する方法を知りません)

例:

_import multiprocessing
from time import sleep

def init(queue):
    global idx
    idx = queue.get()

def f(x):
    global idx
    process = multiprocessing.current_process()
    sleep(1)
    return (idx, process.pid, x * x)

ids = [0, 1, 2, 3]
manager = multiprocessing.Manager()
idQueue = manager.Queue()

for i in ids:
    idQueue.put(i)

p = multiprocessing.Pool(8, init, (idQueue,))
print(p.map(f, range(8)))
_

出力:

_[(0, 8289, 0), (1, 8290, 1), (2, 8294, 4), (3, 8291, 9), (0, 8289, 16), (1, 8290, 25), (2, 8294, 36), (3, 8291, 49)]
_

プールには4つの異なるpidしかありませんが、プールには8つのプロセスが含まれ、1つのidxは1つのプロセスでのみ使用されることに注意してください。

3
Steohan

私はこれをスレッドで行い、最終的に a queue を使用してジョブ管理を処理しました。これがベースラインです。私の完全なバージョンには_try-catches_の束があります(特にワーカーでは、q.task_done()が失敗しても呼び出されることを確認します)。

_from threading import Thread
from queue import Queue
import time
import random


def run(idx, *args):
    time.sleep(random.random() * 1)
    print idx, ':', args


def run_jobs(jobs, workers=1):
    q = Queue()
    def worker(idx):
        while True:
            args = q.get()
            run(idx, *args)
            q.task_done()

    for job in jobs:
        q.put(job)

    for i in range(0, workers):
        t = Thread(target=worker, args=[i])
        t.daemon = True
        t.start()

    q.join()


if __== "__main__":
    run_jobs([('job', i) for i in range(0,10)], workers=5)
_

マルチプロセッシングを使用する必要はありませんでしたが(ワーカーは外部プロセスを呼び出すためだけです)、これを拡張することができました。マルチプロセッシング用のAPIは、それを少し変更します。次のように適応できます。

_from multiprocessing import Process, Queue
from Queue import Empty
import time
import random

def run(idx, *args):
    time.sleep(random.random() * i)
    print idx, ':', args


def run_jobs(jobs, workers=1):
    q = Queue()
    def worker(idx):
        try:
            while True:
                args = q.get(timeout=1)
                run(idx, *args)
        except Empty:
            return

    for job in jobs:
        q.put(job)

    processes = []
    for i in range(0, workers):
        p = Process(target=worker, args=[i])
        p.daemon = True
        p.start()
        processes.append(p)

    for p in processes: 
        p.join()


if __== "__main__":
    run_jobs([('job', i) for i in range(0,10)], workers=5)
_

両方のバージョンは次のようなものを出力します:

_0 : ('job', 0)
1 : ('job', 2)
1 : ('job', 6)
3 : ('job', 3)
0 : ('job', 5)
1 : ('job', 7)
2 : ('job', 1)
4 : ('job', 4)
3 : ('job', 8)
0 : ('job', 9)
_
1
RyanD