web-dev-qa-db-ja.com

pythonマルチプロセッシングプールをターミナルとコードモジュールでDjangoまたはFlask

Multiprocessing.Pool in pythonを次のコードで使用すると、奇妙な動作が発生します。

from multiprocessing import Pool
p = Pool(3)
def f(x): return x
threads = [p.apply_async(f, [i]) for i in range(20)]
for t in threads:
    try: print(t.get(timeout=1))
    except Exception: pass

次のエラーが3回(プール内のスレッドごとに1回)発生し、「3」から「19」が出力されます。

AttributeError: 'module' object has no attribute 'f'

最初の3つのapply_async呼び出しは返されません。

一方、私が試してみると:

from multiprocessing import Pool
p = Pool(3)
def f(x): print(x)
p.map(f, range(20))

AttributeErrorを3回取得すると、シェルは「6」から「19」を出力し、ハングして[Ctrl] + [C]で強制終了できません。

マルチプロセッシングドキュメントには、次のように書かれています。

このパッケージ内の機能では、mainモジュールが子によってインポート可能である必要があります。

これは何を意味するのでしょうか?

明確にするために、私は機能をテストするためにターミナルでコードを実行していますが、最終的にはこれをWebサーバーのモジュールに配置できるようにしたいと考えています。 pythonターミナルとコードモジュールでmultiprocessing.Poolを適切に使用するにはどうすればよいですか?

19
Zags

これが意味することは、プールで実行される関数の定義の後にプールを初期化する必要があるということです。スタンドアロンスクリプトを作成している場合、if __name__ == "__main__":ブロック内でプールを使用することは機能しますが、これは、より大きなコードベースまたはサーバーコード(DjangoやFlaskなど)では不可能です。 _ 事業)。したがって、これらのいずれかでプールを使用しようとしている場合は、以下のセクションで説明されているこれらのガイドラインに必ず従ってください。

  1. モジュールの下部または関数内でプールを初期化します。
  2. モジュールのグローバルスコープ内のプールのメソッドを呼び出さないでください。

または、I/O(データベースアクセスやネットワーク呼び出しなど)でより優れた並列処理のみが必要な場合は、このような頭痛の種をすべて軽減し、プロセスのプールの代わりにスレッドのプールを使用できます。これには、完全に文書化されていないものが含まれます。

from multiprocessing.pool import ThreadPool

インターフェイスはPoolのインターフェイスとまったく同じですが、プロセスではなくスレッドを使用するため、プロセスプールを使用する場合の警告はありません。唯一の欠点は、コード実行の真の並列処理が得られないことです。 I/Oのブロックにおける並列処理。


プールで実行する関数の定義後に、プールを初期化する必要があります

python docsからの不可解なテキストは、プールが定義されたときに、周囲のモジュールがプール内のスレッドによってインポートされることを意味します。 pythonターミナルの場合、これはこれまでに実行したすべてのコードのみを意味します。

したがって、プールで使用する関数は、プールを初期化する前に定義する必要があります。これは、モジュール内のコードとターミナル内のコードの両方に当てはまります。問題のコードの次の変更は正常に機能します。

from multiprocessing import Pool
def f(x): return x  # FIRST
p = Pool(3) # SECOND
threads = [p.apply_async(f, [i]) for i in range(20)]
for t in threads:
    try: print(t.get(timeout=1))
    except Exception: pass

または

from multiprocessing import Pool
def f(x): print(x)  # FIRST
p = Pool(3) # SECOND
p.map(f, range(20))

罰金とは、Unixでは罰金を意味します。 Windowsには独自の問題があり、ここでは取り上げません。


モジュールでプールを使用する際の警告

しかし、待ってください(他の場所にインポートしたいモジュールでプールを使用すること)はまだまだあります!

関数内でプールを定義する場合、問題はありません。 ただし、モジュールでグローバル変数としてPoolオブジェクトを使用している場合は、ページのbottomで定義する必要があります。トップ。これはほとんどの優れたコードスタイルに反しますが、機能のために必要です。ページの上部で宣言されたプールを使用する方法は、次のように、他のモジュールからインポートされた関数でのみ使用することです。

from multiprocessing import Pool
from other_module import f
p = Pool(3)
p.map(f, range(20))

別のモジュールから事前構成されたプールをインポートすることは、次のように、実行したいものの後にインポートする必要があるため、かなり恐ろしいものです。

### module.py ###
from multiprocessing import Pool
POOL = Pool(5)

### module2.py ###
def f(x):
    # Some function
from module import POOL
POOL.map(f, range(10))

そして第二に、インポートしているモジュールのグローバルスコープ内のプールで何かを実行すると、システムがハングします。つまり、これは機能しません

### module.py ###
from multiprocessing import Pool
def f(x): return x
p = Pool(1)
print(p.map(f, range(5)))

### module2.py ###
import module

ただし、module2をインポートするものがない限り、これはは機能します

### module.py ###
from multiprocessing import Pool

def f(x): return x
p = Pool(1)
def run_pool(): print(p.map(f, range(5)))

### module2.py ###
import module
module.run_pool()

さて、これの背後にある理由はもっと奇妙なだけであり、問​​題のコードが属性エラーを1回だけ吐き出し、その後はコードを正しく実行しているように見えるという理由に関連している可能性があります。また、プールスレッド(少なくともある程度の信頼性はある)が実行後にモジュール内のコードをリロードするようです。

36
Zags

スレッドプールで実行する関数は、プールを作成するときにすでに定義されている必要があります。

これは機能するはずです:

from multiprocessing import Pool
def f(x): print(x)
if __name__ == '__main__':
    p = Pool(3)
    p.map(f, range(20))

その理由は、(少なくともforkを持つシステムでは)プールを作成するときに、現在のプロセスをフォークすることによってワーカーが作成されるためです。そのため、その時点でターゲット関数がまだ定義されていない場合、ワーカーはそれを呼び出すことができません。

Windowsにはforkがないため、Windowsでは少し異なります。ここで、新しいワーカープロセスが開始され、メインモジュールがインポートされます。そのため、Windowsでは、実行中のコードをif __name__ == '__main__'で保護することが重要です。そうしないと、新しいワーカーがそれぞれコードを再実行するため、新しいプロセスが無限に生成され、プログラム(またはシステム)がクラッシュします。

4
mata

このエラーの別の考えられる原因があります。サンプルコードを実行すると、このエラーが発生しました。

ソースは、マルチプロセッシングを正しくインストールしたにもかかわらず、C++コンパイラが私のシステムにインストールされていなかったということでした。マルチプロセッシングを更新しようとしたときに、何かピップが私に知らせました。したがって、コンパイラがインストールされていることを確認する価値があるかもしれません。

0
ic_fl2