Как вызвать функцию Python в качестве обратного вызова внутри потока C ++, используя pybind11 - PullRequest
1 голос
/ 26 февраля 2020

Я разработал систему 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 функционирует как обратные вызовы и позволяет избежать тупиков?

1 Ответ

1 голос
/ 26 февраля 2020

Благодаря этому обсуждению и многим другим ресурсам ( 1 , 2 , 3 ) я понял, что защита функций, которые запуск и присоединение к потоку C ++ с gil_scoped_release, кажется, решает проблему:

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 ++. Я до сих пор не уверен, верны ли мои рассуждения, поэтому буду признателен любому эксперту за комментарии.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...