SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

Pythonの新機能を知ろう!

Python 3.14の新機能――フリースレッドPythonの正式サポートと高速化などを解説

Pythonの新機能を知ろう! 第7回

標準ライブラリ内の複数のインタプリタ[3.14]

 Python 3.14では、同一プロセス内で複数のインタプリタを実行することがモジュールレベルで可能になりました。

 従来は、この機能はC-API(C言語から利用することを前提としたAPI群)においてのみ利用可能でしたが、concurrent.interpretersモジュールが提供されるようになり、Pythonコードから直接複数インタプリタを利用できるようになりました。

 同一プロセス内で複数のインタプリタを実行する意味は、上記のグローバルインタプリタロックが有効なインタプリタにあります。このインタプリタは、マルチスレッド処理を十分に高速化できないといった課題がありました。

 そこで、インタプリタ自体をスレッドとして起動し、シングルスレッド実行でマルチコアCPUのメリットを最大限に生かそうというのが複数インタプリタです。

ベーシックなconcurrent.interpretersモジュール

 concurrent.interpretersモジュールは、インタプリタの起動や関数の呼び出し、コードの実行などを司ります。ベーシックな機能を提供しており、とりあえず複数のインタプリタを使いたいのであれば、このモジュールを使うことになります。

 基本的には、インタプリタのためのスレッドを起動し、その中でInterpreterインスタンスを生成、そのインスタンスに対して関数の呼び出しやコードの実行、__main__モジュールに対するオブジェクトの引き渡しを行っていきます。

 以下のリストは、この手順でインタプリタを複数回起動して関数を呼び出し、情報を表示するものです。

リスト multi-interpreter.py
import concurrent.interpreters as interpreters
import os, threading

def worker(arg):
    print(f"Start thread: id={threading.get_ident()}", flush=True)
    # 新しい Python インタプリタ(サブインタプリタ)を作成
    interp = interpreters.create()	(1)
    print(f"Start interpreter: id={interp.id}", flush=True)	(2)
    # サブインタプリタ内で実行する関数を定義
    def func(arg):	(3)
        print(f"Start function: arg={arg}", flush=True)
        return arg
    # サブインタプリタ内でfuncを呼び出して結果を表示
    result = interp.call(func, arg)	(4)
    print(f"Interpreter result: {result}", flush=True)
    # サブインタプリタを閉じてリソースを解放
    interp.close()	(5)

if __name__ == "__main__":
    args = [100, 200, 300]
    print(f"CPython pid={os.getpid()}, thread id={threading.get_ident()}", flush=True)
    # それぞれの引数ごとにスレッドを作成&起動
    threads = [threading.Thread(target=worker, args=(i,)) for i in args]
    for t in threads:
        t.start()

 Interpreterクラスのメソッドについて補足します。(1)はインスタンスの生成で基本的にcreateメソッドでインスタンスを生成して使うことになります。(2)でインタプリタのIDを取得でき、(4)のcallメソッドで(3)のような関数を引数付きで呼び出し、戻り値を受け取ることができます。不要になったインタプリタは(5)のようにcloseメソッドで破棄します。

図 concurrent.interpretersモジュールによる複数インタプリタ
図 concurrent.interpretersモジュールによる複数インタプリタ

 実行すると、以下のようになります(実行のたびに表示順は変化します)。インタプリタのIDは1からの整数値となるようです。

CPython pid=30549, thread id=140704305471680
Start thread: id=123145505316864
Start thread: id=123145522106368
Start thread: id=123145538895872
Start interpreter: id=1
Start function: arg=100
Interpreter result: 100
Start interpreter: id=3
Start interpreter: id=2
Start function: arg=300
Interpreter result: 300
Start function: arg=200
Interpreter result: 200

 ちなみに、callメソッドの代わりに、スクリプトを文字列で渡して実行できるexecメソッド、スレッドを生成して関数を呼び出せるcall_in_threadメソッドも利用可能です。

より抽象化されたconcurrent.futuresモジュール

 concurrent.interpretersはベーシックな機能を提供してくれますが、スレッドの起動が必要など手順がやや煩雑で、またスレッドやプロセスといった並列化とは手順が異なるのが難点です。

 そこで、並列化の機能を提供するconcurrent.futuresモジュールに新たにInterpreterPoolExecutorクラスが提供されるようになりました。InterpreterPoolExecutorクラスは、従来からのThreadPoolExecutor、ProcessPoolExecutorと同様のインタフェース(例えばmapなど)で使うことができ、より容易に複数インタプリタを利用できます。

 以下のリストは、ある自然数を上限とした素数をカウントする関数を、別インタプリタで複数回呼び出して情報を表示するものです。

リスト multi-interpreter-futures.py
from concurrent.futures import InterpreterPoolExecutor
import os, threading, sys, math

# 素数カウント(処理内容は複数インタプリタと無関係なので省略)
def count_primer(upper: int):
    print(f"Start interpreter: upper={upper}, thread id={threading.get_ident()} main id={id(sys.modules['__main__'])}", flush=True)	(1)
   …略…
    print('End', flush=True)
    return count

if __name__ == "__main__":
    # 並列に処理したいタスクの入力(それぞれ「上限値(upper)」)
    pattern = [1_000_000, 500_000, 100_000]
    print(f"CPython pid={os.getpid()}, main id={id(sys.modules['__main__'])}", flush=True)
    # InterpreterPoolExecutorで並列にタスクを実行
    with InterpreterPoolExecutor() as pool:	(2)
        results = list(pool.map(count_primer, pattern))
    # 並列処理の結果をまとめて表示
    print(f"Results: {results}", flush=True)

 InterpreterPoolExecutorクラスのメソッドなどについても補足します。

 (1)においては、起動したインタプリタおよび呼び出した関数の情報を出力しています。関数の引数(上限の自然数)、スレッドID、__main__モジュールのIDを出力させて、インタプリタとスレッドの情報を得ています。

 (2)以降がインタプリタの起動部分で、InterpreterPoolExecutorクラスのインスタンスからmapメソッドで関数を呼び出し、第2引数に3要素のリストを与えることで3回インタプリタを起動します。全て実行が終了すれば、結果をリストにして出力して終了です。

 実行すると、以下のようになります(実行のたびに表示順は変化します)。

CPython pid=31133, main id=4444602128
Start interpreter: upper=1000000, thread id=123145451397120 main id=4458833504
Start interpreter: upper=500000, thread id=123145468186624 main id=4459882080
Start interpreter: upper=100000, thread id=123145484976128 main id=4460930656
End
End
End
Results: [78498, 41538, 9592]

 注目すべきはmain idで、ここでは__main__モジュールのIDをsys.modulesから取得していますが、3回の関数呼び出しで全てIDが異なることが分かります。また、各インタプリタは別スレッドで起動するので、スレッドIDも変わってきます。

図 concurrent.futuresモジュールによる複数インタプリタ
図 concurrent.futuresモジュールによる複数インタプリタ

次のページ
安全な外部デバッガインタフェース[3.14]

この記事は参考になりましたか?

Pythonの新機能を知ろう!連載記事一覧

もっと読む

この記事の著者

WINGSプロジェクト 山内 直(WINGSプロジェクト ヤマウチ ナオ)

WINGSプロジェクトについて>有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティ(代表 山田祥寛)。主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手がける。2018年11月時点での登録メンバは55名で、現在も執筆メンバを募集中。興味のある方は、どしどし応募頂きたい。著書記事多数。 RSS X: @WingsPro_info(公式)、@WingsPro_info/wings(メンバーリスト) Facebook <個人紹介>WINGSプロジェクト所属のテクニカルライター。出版社を経てフリーランスとして独立。ライター、エディター、デベロッパー、講師業に従事。屋号は「たまデジ。」。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

山田 祥寛(ヤマダ ヨシヒロ)

静岡県榛原町生まれ。一橋大学経済学部卒業後、NECにてシステム企画業務に携わるが、2003年4月に念願かなってフリーライターに転身。Microsoft MVP for Visual Studio and Development Technologies。執筆コミュニティ「WINGSプロジェクト」代表。主な著書に「独習シリーズ(Java・C#・Python・PHP・Ruby・JSP&サーブレットなど)」「速習シリーズ(ASP.NET Core・Vue.js・React・TypeScript・ECMAScript、Laravelなど)」「改訂3版JavaScript本格入門」「これからはじめるReact実践入門」「はじめてのAndroidアプリ開発 Kotlin編 」他、著書多数

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/22812 2025/12/26 11:00

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング