Python の ThreadPoolExecutor は max_workers を適切に指定しないとゾンビのプロセスが生まれるかもしれない
注意:この記事は情報に誤りがあるかもしれない
Python の ThreadPoolExecutor は基本的に with 内で動かすが、実際のスレッド数を超える値を max_workers に指定すると予想外のことが起きる。
例えば 3 つのプロセスを並列化したいとき、max_workers を 4 にすると、4 - 3 = 1 のゾンビ・プロセスが生まれる。全体のプログラムがデーモン化されている場合、この 1 個のスレッドが with で回収されない。
例えば gunicorn で動かす Django または Flask のプログラム内に ThreadPoolExecutor で並列化した関数があるとする。この ThreadPoolExecutor で max_workers を関数の数よりも多く設定していると
13474 gunicorn --bind 127.0.0.1:8080 main:app --daemon
13510 gunicorn --bind 127.0.0.1:8080 main:app --daemon
13511 gunicorn --bind 127.0.0.1:8080 main:app --daemon
13512 gunicorn --bind 127.0.0.1:8080 main:app --daemon
13513 gunicorn --bind 127.0.0.1:8080 main:app --daemon
13514 gunicorn --bind 127.0.0.1:8080 main:app --daemon
13549 gunicorn --bind 127.0.0.1:8080 main:app --daemon
13550 gunicorn --bind 127.0.0.1:8080 main:app --daemon
13551 gunicorn --bind 127.0.0.1:8080 main:app --daemon
13552 gunicorn --bind 127.0.0.1:8080 main:app --daemon
13553 gunicorn --bind 127.0.0.1:8080 main:app --daemon
13578 gunicorn --bind 127.0.0.1:8080 main:app --daemon
13579 gunicorn --bind 127.0.0.1:8080 main:app --daemon
13580 gunicorn --bind 127.0.0.1:8080 main:app --daemon
13581 gunicorn --bind 127.0.0.1:8080 main:app --daemon
13582 gunicorn --bind 127.0.0.1:8080 main:app --daemon
13586 gunicorn --bind 127.0.0.1:8080 main:app --daemon
となる。これらのゾンビ・デーモンはシグナルで強制終了されない限り消えない。
デーモンになっていないプログラム、例えばローカルで動かしている Flask では max_workers の値にかかわらず、余ったスレッドは with で消される(たぶん)。
また ThreadPoolExecutor は OS に依存するため、実際は Pebble などを使う。Python の並列化でシグナルを直接扱っているソースコードをよく見かけるが、たぶん避けたほうがいい。
システム・環境
-
Pythonの環境設定0166
-
Pythonのプログラムを終了する0339
-
Pythonのコメントアウト(コメントの書き方)0180