首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何在C++线程中使用pybind11调用Python函数作为回调

如何在C++线程中使用pybind11调用Python函数作为回调
EN

Stack Overflow用户
提问于 2020-02-26 09:01:38
回答 2查看 4K关注 0票数 5

我设计了一个C++系统,它从运行在单独线程中的过程中调用用户定义的回调。简化的system.hpp如下所示:

代码语言:javascript
复制
#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进行测试。

代码语言:javascript
复制
#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几次,然后停止。但是,我遇到了一个问题,试图为我的System创建python绑定。如果我将python函数注册为回调函数,我的程序将在调用System::stop后陷入死锁。我稍微研究了一下这个话题,似乎我面对的是吉尔的问题。可复制的例子:

binding.cpp

代码语言:javascript
复制
#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脚本:

代码语言:javascript
复制
#!/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文档端获取或释放GIL的可能性的C++部分。但是,我没有设法摆脱在我的情况下发生的死锁:

代码语言:javascript
复制
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;,我会得到

致命Python错误: PyEval_SaveThread: NULL tstate

如何将python函数注册为回调并避免死锁?

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2020-02-26 16:02:17

多亏了这一讨论和许多其他资源(123.),我发现保护使用gil_scoped_release启动和加入C++线程的函数似乎解决了这个问题:

代码语言:javascript
复制
PYBIND11_MODULE(mysystembinding, m) {
  py::class_<System>(m, "System")
    .def(py::init<>())
    .def("start", &System::start, py::call_guard<py::gil_scoped_release>())
    .def("stop", &System::stop, py::call_guard<py::gil_scoped_release>())
    .def("registerCallback", &System::registerCallback);
}

显然,出现死锁是因为python在调用负责C++线程操作的绑定时持有锁。我仍然不确定我的推理是否正确,所以我会感谢任何专家的意见。

票数 5
EN

Stack Overflow用户

发布于 2020-08-19 15:09:08

在我的情况下,在gil_scoped_release消除死锁之前调用join()

代码语言:javascript
复制
void Tick::WaitLifeOver() {
  if (thread_.joinable()) {
    thread_.join();
  }
}
代码语言:javascript
复制
PYBIND11_MODULE(tick_pb, m) {
  py::class_<Tick, std::shared_ptr<Tick>>(m, "Tick")
    // ...
    .def("wait_life_over", &Tick::WaitLifeOver,
        py::call_guard<py::gil_scoped_release>());
}

这是代码:C++线程回调Python函数

票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/60410178

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档