結論:Python 3.14ではforkはどのOSでも既定ではありません。Linuxなどforkserver対応POSIXでは既定がforkserverへ変わり、WindowsとmacOSはspawnです。従来のforkで動いていたコードは、親の状態を引き継ぐ前提、mainの副作用、pickleできない関数やデータが表面化します。まず実際の起動方式を確認し、ワーカーをimport可能な形へ直します。

1. Python・OS・起動方式を確認する
次をプロジェクト直下のcheck_start_method.pyとして保存し、問題が出る環境のPythonで実行します。
import sys
import multiprocessing as mp
print(sys.version)
print(sys.platform)
print(mp.get_all_start_methods())
print(mp.get_start_method())
プロジェクト直下で、対象のPython環境を使って実行します。
python check_start_method.py
get_all_start_methods()の先頭は環境の既定です。ただし、ProcessPoolExecutorにmp_contextを渡せばその指定が優先されます。さらに、max_tasks_per_childを指定してmp_contextを省略するとspawnが使われます。「Pythonの既定がforkserverだから、このExecutorもforkserver」とは限りません。生成箇所の両引数も確認してください。
| 環境 | Python 3.14の基本的な既定 | 注意 |
|---|---|---|
| Linux等の対応POSIX | forkserver | 実際の一覧で対応確認 |
| macOS | spawn | forkは非推奨 |
| Windows | spawn | forkserver・forkは非対応 |
2. BrokenProcessPoolだけで原因を決めない
BrokenProcessPoolはワーカーが正常でない形で終了した結果で、原因名ではありません。直前の子プロセス側トレースバックや標準エラーまで読み、次を分けます。
- 子がmainをimportした際、トップレベルで再びプロセスを作っていないか
- callable、引数、返り値がpickle可能か。lambda、ローカル関数、REPL関数ではないか
- 親で変更したglobalを子も同じ値で見る前提になっていないか
- initializer、依存のimport、ネイティブ拡張、OOMやシグナル終了ではないか
次は、spawnやforkserverで問題になるトップレベル実行の例です。
from concurrent.futures import ProcessPoolExecutor
def square(number):
return number * number
with ProcessPoolExecutor(max_workers=2) as executor:
print(list(executor.map(square, [1, 2, 3])))
子がファイルをimportするとExecutor作成まで再実行されます。プロセス作成はmainガード内へ移し、ワーカーはlambdaやmain内のローカル関数ではなく、モジュールのトップレベルへ置きます。
3. mainガードとcontextを明示して直す
次をプロジェクト直下のprocesspool_fixed.pyとして保存します。指定方式が利用可能かを確認し、そのcontextをExecutorへ渡します。
import argparse
import multiprocessing as mp
from concurrent.futures import ProcessPoolExecutor
def square(number):
return number * number
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--method', default=None)
args = parser.parse_args()
if args.method and args.method not in mp.get_all_start_methods():
raise SystemExit('unsupported start method: ' + args.method)
context = mp.get_context(args.method)
print('method:', context.get_start_method())
with ProcessPoolExecutor(max_workers=2, mp_context=context) as executor:
print(list(executor.map(square, [1, 2, 3])))
if __name__ == '__main__':
main()
Windows・macOS・Linuxで、まず既定contextを確認します。
python processpool_fixed.py
次はforkserver対応Linux等のPOSIX環境で、起動方式の一覧にforkserverがある場合だけ実行します。Windows・macOSでの既定方式確認には使いません。
python processpool_fixed.py --method forkserver
期待値は環境依存です。Windows/Python 3.12.14では--method spawnでmethod: spawnと[1, 4, 9]を実測し、forkserverは非対応として停止することを確認しています。Linux/Python 3.14のforkserver実行は本稿では未実測で、挙動の説明は公式資料に基づきます。REPLやNotebookの一時定義と、ファイル実行を混同しないでください。
4. globalとimport時の副作用を整理する
spawnとforkserverでは、子が見るglobalが親のProcess.start()時点と同じとは限りません。必要な設定や処理対象は引数またはinitializerの引数で渡します。import時にファイル操作、接続、Executor作成を行わず、実行処理はmainガード内へ置きます。
5. forkへ戻すのは限定措置
対応POSIXで旧コードを一時的に動かす場合は、利用可能なことを確認して明示します。
次は上の例のimportとsquareを使い、main()内のcontext作成・Executor部分を置き換える断片です。main()内に合わせてインデントし、mainガードを保持してください。この断片だけでは単独実行できません。
context = mp.get_context('fork')
with ProcessPoolExecutor(mp_context=context) as executor:
print(list(executor.map(square, [1, 2, 3])))
forkはマルチスレッドとの組み合わせに問題があり、Python 3.12以降は検出時に警告対象です。Windowsでは使えず、macOSでも推奨されません。移行の第一選択ではなく、限定的な互換措置にしてください。max_tasks_per_childはforkと非互換です。
6. mainとpickleを直してもhangする場合
Pythonのパッチ版も確認します。公式3.14.8ドキュメントでは、3.14.7で、ワーカーがmax_tasks_per_child上限に達して終了し、待機タスクが残るとExecutorが停止する不具合を修正したとされています。該当条件なら3.14.7以降へ更新します。ほかにinitializer例外、ネイティブクラッシュ、OOM、子からExecutorやFutureのメソッドを呼ぶデッドロックも確認してください。
FAQ
Q. Python 3.14のLinuxなら必ずforkserverですか?
対応POSIXでは既定ですが、mp_contextやmax_tasks_per_childで変わります。実測値と生成コードを確認します。
Q. mainガードを付ければlambdaも使えますか?
いいえ。mainガードとpickle要件は別です。lambda、ローカル関数、REPL関数は期待どおり動くと保証されません。
Q. BrokenProcessPoolはpickleエラーですか?
断定できません。initializer失敗、import例外、ネイティブクラッシュ、外部終了などでも発生します。
一次資料
確認日:2026年10月5日。

コメント