背景
- C++ 側で thread 使うコード(e.g. OpenGL window を表示する)を書いていて, それを pybind11 で wrap したい.
普通にやるとクラッシュして何が原因なの? と半営業日くらい原因不明で悩んでしまいます
GIL の管理が必要です.
構成
以下のように, loop のある関数を C++11 thread で書いているとします
(実際は thread 化してなくても同様と思われる. また pthread など他のスレッド関数使っても同様)
void renderThread(std::atomic<bool> *quit) {
while (...) {
if (quit && ((*quit) == true)) {
break;
}
draw();
}
}
...
void start_opengl() {
_quit = false;
_th = std::thread(renderThread, &quit);
}
void end_opengl() {
_quit = false;
_th.join();
}
ここで start_opengl()
や end_opengl()
をそのまま pybind11 で wrap して呼び出すとクラッシュします.
GIL が原因でした.
にあるように, 実行が長くなるコード(or thread を起動するコード)には GIL release
py::gil_scoped_release guard;
を, thread が終了する(join()
を呼ぶところ)には GIL acquire
py::gil_scoped_acquire guard;
をはさみます. C++ コードに記入だと pybind11 ヘッダのインクルードが発生して面倒なので, pybind11 binding のところで定義するのがよいでしょう.
m.def("start_opengl", []() {
py::gil_scoped_release guard;
start_opengl();
});
m.def("end_opengl", []() {
py::gil_scoped_aquire guard;
end_opengl();
});
のように.
ただ, C++ からさらに python を呼んだりと複雑な場合はこれではうまくいかなそうです!
上記 github issue にあるように, pybind11 GIL aquire/release は諦め, python multiprocessing で python 世界でスレッド管理するようにするのがよさそうです.
TODO
- Python -> C++ thread へ中断やデータを渡すうまい方法を考える
- とりあえず中断とかは
std::atomic<bool>
でフラグでいけるが... JSON-RPC あたりで http メッセージ通信とかにしたほうがいいかも(C++ 側で JSON パースが面倒であるのと, そこまでやるなら C++ と python でプロセス切り分けたほうが良さそうであるが...)
- とりあえず中断とかは