web-dev-qa-db-ja.com

イニシャライザを使用してマルチプロセスプールを設定する方法

マルチプロセスプールオブジェクトを使用しようとしています。各プロセスが開始時にデータベース接続を開き、その接続を使用して渡されるデータを処理するようにします(データのビットごとに接続を開いたり閉じたりするのではなく)。これはイニシャライザのようですが、ワーカーとイニシャライザーが通信する方法について頭を悩ますことはできません。だから私はこのようなものを持っています:

def get_cursor():
  return psycopg2.connect(...).cursor()

def process_data(data):
   # here I'd like to have the cursor so that I can do things with the data

if __== "__main__":
  pool = Pool(initializer=get_cursor, initargs=())
  pool.map(process_data, get_some_data_iterator())

カーソルをget_cursor()からprocess_data()に戻す方法(または方法)

45
Chris Curvey

したがって、初期化関数が呼び出されます。

def worker(...):
    ...
    if initializer is not None:
        initializer(*args)

そのため、どこにも保存された戻り値はありません。あなたはこれがあなたの運命だと思うかもしれませんが、違います!各ワーカーは個別のプロセスにあります。したがって、通常のglobal変数を使用できます。

これは正確ではありませんが、動作します:

cursor = None
def set_global_cursor(...):
    global cursor
    cursor = ...

これで、process_data関数でcursorを使用できます。個別のプロセス内のcursor変数は、他のすべてのプロセスとは別個であるため、お互いに踏むことはありません。

(最初にmultiprocessingを使用することを伴わない、psycopg2がこれに対処する別の方法を持っているかどうかはわかりません。これは、multiprocessingモジュール。

81
torek

torekは、この場合にイニシャライザが動作しない理由についてすでに十分な説明をしています。ただし、私はGlobal variableのファンではないので、別のソリューションをここに貼り付けたいと思います。

アイデアは、クラスを使用して関数をラップし、「グローバル」変数でクラスを初期化することです。

class Processor(object):
  """Process the data and save it to database."""

  def __init__(self, credentials):
    """Initialize the class with 'global' variables"""
    self.cursor = psycopg2.connect(credentials).cursor()

  def __call__(self, data):
    """Do something with the cursor and data"""
    self.cursor.find(data.key)

そして、で呼び出す

p = Pool(5)
p.map(Processor(credentials), list_of_data)

そのため、最初のパラメーターは資格情報でクラスを初期化し、クラスのインスタンスを返し、データでインスタンスをマップ呼び出します。

これはグローバル変数のソリューションほど簡単ではありませんが、グローバル変数を避け、安全な方法で変数をカプセル化することを強くお勧めします。 (そして、いつかラムダ式をサポートできることを本当に願っています、それは物事をはるかに簡単にするでしょう...)

11
yeelan

関数を初期化子に送信して、その中に接続を作成することもできます。その後、カーソルを関数に追加します。

def init_worker(function):
    function.cursor = db.conn()

これで、グローバルを使用せずにfunction.cursorを介してdbにアクセスできるようになりました。次に例を示します。

def use_db(i):
    print(use_db.cursor) #process local
pool = Pool(initializer=init_worker, initargs=(use_db,))
pool.map(use_db, range(10))
9
The Unfun Cat

イニシャライザでグローバル変数を定義することは一般的に望ましくないことを考えると、それらの使用を避け、各サブプロセス内の単純なキャッシングで各呼び出し内でコストのかかる初期化を繰り返すことも回避できます。

from functools import lru_cache
from multiprocessing.pool import Pool
from time import sleep


@lru_cache(maxsize=None)
def _initializer(a, b):
    print(f'Initialized with {a}, {b}')


def _pool_func(a, b, i):
    _initializer(a, b)
    sleep(1)
    print(f'got {i}')


arg_a = 1
arg_b = 2

with Pool(processes=5) as pool:
    pool.starmap(_pool_func, ((arg_a, arg_b, i) for i in range(0, 20)))

出力:

Initialized with 1, 2
Initialized with 1, 2
Initialized with 1, 2
Initialized with 1, 2
Initialized with 1, 2
got 1
got 0
got 4
got 2
got 3
got 5
got 7
got 8
got 6
got 9
got 10
got 11
got 12
got 14
got 13
got 15
got 16
got 17
got 18
got 19
5
mcguip