C++言語

pybind11を使うときの注意点、インタプリタの持ち方【3選】

はじめに

以下の記事でご紹介した C++ と Python をつなぐ便利な pybind11 ですが、
Python インタプリタを使用する際に制約があります。

参考pybind11でPython関数の引数にC++のインスタンスを渡す方法

AIの分野では、Pythonでアルゴリズム検討を行うケースが増えており、最近C++とPythonを繋げるためのライブラリpybind11が注目されています。本記事ではC++で定義したクラスのインスタンスを引数に、Pythonの関数を呼び出す方法を紹介します。

続きを見る

本記事では、制約内容と、それを回避する方法をいくつかご紹介いたします。

 

制約の内容

① 生成できるインタプリタは1つだけ

先ず、公式「Interpreter lifetime」に記載されている通り、複数インタプリタを生成することができません。

Creating two concurrent scoped_interpreter guards is a fatal error.

正確には、1 つのプロセスで 1 つのみ生成できます。

SANACHAN
SANACHAN
つまり、スレッドセーフではないということです。

 

② インタプリタを生成したスレッドからのみ呼び出せる

Python および pybind11 には、GIL(Global Interpreter Lock)と呼ばれる仕組みがあります。
詳しくは公式「Global Interpreter Lock」に記載されていますが、複数スレッドから Python オブジェクトへ
安全にアクセスするための仕組みです。

The Python C API dictates that the Global Interpreter Lock (GIL) must always be held by the current thread to safely access Python objects.

SANACHAN
SANACHAN
Python オブジェクトへのアクセスはスレッドセーフに設計されています。

 

ある特定の処理は他のスレッドから呼び出すことが可能ですが、呼び出せない処理もあり、
インタプリタ内部で実行される処理をいちいち考えながらコーディングするのは面倒なため、
「呼び出せない」と考えた方が無難でしょう。

内部では PyGILState_Check という関数で Lock の取得状態を確認しながら処理を進めているため、
PyGILState_Check() failure.」のようなエラーが出たら、この制約に引っかかっていることになります。

 

インタプリタの持ち方

では、これらの制約を回避しつつ、C++ で汎用的に実装するにはどのようにすれば良いのでしょうか。
ここではいくつかのユースケースと実装例をご紹介いたします。

 

ケース① 毎回生成

インタプリタを使用する際、都度インスタンスを生成します。
多くのサンプルでも記述されているように、一番簡単な使い方となります。

py::scoped_interpreter は、その名の通りスマートポインタを利用しており、
インタプリタは、guard 変数のインスタンスと同じライフサイクルをたどります。

  • コンストラクタで Py_Initialize() などが呼び出されてワールド初期化
  • デストラクタで Py_Finalize() が呼び出されてワールド解放

という感じです。

メリット

局所的なライフサイクルとなるため、複雑なことを考えなくてよい。

デメリット

ワールドが解放されるため、毎回 import や Python コード側の初期化処理などの実行が必要になる。

 

ケース② 他のクラスと合わせる

C++ で実装する場合、クラスインスタンスのライフサイクルに合わせたくなります。
この場合は以下のように記述すると、インタプリタのライフサイクルを合わせることができます。

Python のインタプリタは 1 プロセスに 1 つしか利用できないため、
クラスを Singleton にして保証しています。

メリット

オブジェクト指向に沿った設計を行いやすい。

デメリット

インスタンスを生成したスレッドから Execute を呼び出す必要がある。

 

ケース③ インタプリタだけ外出し

クラスのインスタンスを生成するスレッドと、実際に Python インタプリタを利用するスレッドが異なる場合、
以下のようにインタプリタの生成だけ外だしにした書き方にすることもできます。

メリット

PyExecutor を生成したスレッド以外から Execute を呼び出せる。

デメリット

Execute を複数インスタンス・スレッドから呼び出せると誤解を与える。

 

参考

 

こちらの記事もよく読まれています

  • この記事を書いた人
  • 最新記事
SANACHAN

SANACHAN

「生涯一エンジニア」を掲げ、大手グローバル企業でSE/PGとして8年勤め、キャリアアップ転職した現役のエンジニアです。世にあるメジャーな全プログラム言語(コボル除く)を自由に扱えます。一児の父。自分のため、家族のため、日々勉強してます。システムエンジニア、プログラミングに関する情報を蓄積している雑記帳です。

-C++言語
-, , ,