Я разработал систему C ++, которая вызывает определенные пользователем обратные вызовы из процедуры, выполняемой в отдельном потоке. Упрощенный system.hpp
выглядит следующим образом:
#pragma once
#include <atomic>
#include <chrono>
#include <functional>
#include <thread>
class System
{
public:
using Callback = std::function<void(int)>;
System(): t_(), cb_(), stop_(true) {}
~System()
{
stop();
}
bool start()
{
if (t_.joinable()) return false;
stop_ = false;
t_ = std::thread([this]()
{
while (!stop_)
{
std::this_thread::sleep_for(std::chrono::milliseconds(100));
if (cb_) cb_(1234);
}
});
return true;
}
bool stop()
{
if (!t_.joinable()) return false;
stop_ = true;
t_.join();
return true;
}
bool registerCallback(Callback cb)
{
if (t_.joinable()) return false;
cb_ = cb;
return true;
}
private:
std::thread t_;
Callback cb_;
std::atomic_bool stop_;
};
Он отлично работает и может быть протестирован на этом коротком примере main.cpp
:
#include <iostream>
#include "system.hpp"
int g_counter = 0;
void foo(int i)
{
std::cout << i << std::endl;
g_counter++;
}
int main()
{
System s;
s.registerCallback(foo);
s.start();
while (g_counter < 3)
{
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
s.stop();
return 0;
}
, который выдаст 1234
несколько раз, а потом остановится. Однако я столкнулся с проблемой при попытке создать python привязок для моего System
. Если я зарегистрирую функцию python в качестве обратного вызова, моя программа заблокируется после вызова System::stop
. Я немного исследовал топи c, и мне кажется, что я столкнулся с проблемой GIL . Воспроизводимый пример:
binding.cpp
:
#include "pybind11/functional.h"
#include "pybind11/pybind11.h"
#include "system.hpp"
namespace py = pybind11;
PYBIND11_MODULE(mysystembinding, m) {
py::class_<System>(m, "System")
.def(py::init<>())
.def("start", &System::start)
.def("stop", &System::stop)
.def("registerCallback", &System::registerCallback);
}
python script:
#!/usr/bin/env python
import mysystembinding
import time
g_counter = 0
def foo(i):
global g_counter
print(i)
g_counter = g_counter + 1
s = mysystembinding.System()
s.registerCallback(foo)
s.start()
while g_counter < 3:
time.sleep(1)
s.stop()
Я прочитал раздел pybind11 docs о возможность приобрести или выпустить GIL на стороне C ++. Однако мне не удалось избавиться от тупика, который возникает в моем случае:
PYBIND11_MODULE(mysystembinding, m) {
py::class_<System>(m, "System")
.def(py::init<>())
.def("start", &System::start)
.def("stop", &System::stop)
.def("registerCallback", [](System* s, System::Callback cb)
{
s->registerCallback([cb](int i)
{
// py::gil_scoped_acquire acquire;
// py::gil_scoped_release release;
cb(i);
});
});
}
Если я позвоню py::gil_scoped_acquire acquire;
до вызова обратного вызова, в любом случае возникает тупик. Если я позвоню py::gil_scoped_release release;
перед вызовом обратного вызова, я получу
Fatal Python error: PyEval_SaveThread: NULL tstate
Что мне нужно сделать, чтобы зарегистрироваться python функционирует как обратные вызовы и позволяет избежать тупиков?