C++ 20 协程与 PyBind11
C++ 20 coroutines with PyBind11
我正在尝试使用 PyBind11 获得一个简单的基于 C++ 20 的生成器模式。这是代码:
#include <pybind11/pybind11.h>
#include <coroutine>
#include <iostream>
struct Generator2 {
Generator2(){}
struct Promise;
using promise_type=Promise;
std::coroutine_handle<Promise> coro;
Generator2(std::coroutine_handle<Promise> h): coro(h) {}
~Generator2() {
if(coro)
coro.destroy();
}
int value() {
return coro.promise().val;
}
bool next() {
std::cout<<"calling coro.resume()";
coro.resume();
std::cout<<"coro.resume() called";
return !coro.done();
}
struct Promise {
void unhandled_exception() {std::rethrow_exception(std::move(std::current_exception()));}
int val;
Generator2 get_return_object() {
return Generator2{std::coroutine_handle<Promise>::from_promise(*this)};
}
std::suspend_always initial_suspend() {
return {};
}
std::suspend_always yield_value(int x) {
val=x;
return {};
}
std::suspend_never return_void() {
return {};
}
std::suspend_always final_suspend() noexcept {
return {};
}
};
};
Generator2 myCoroutineFunction() {
for(int i = 0; i < 100; ++i) {
co_yield i;
}
}
class Gen{
private:
Generator2 myCoroutineResult;
public:
Gen(){
myCoroutineResult = myCoroutineFunction();
}
int next(){
return (myCoroutineResult.next());
}
};
PYBIND11_MODULE(cmake_example, m) {
pybind11::class_<Gen>(m, "Gen")
.def(pybind11::init())
.def("next", &Gen::next);
}
但是我收到一个错误:
Process finished with exit code 139 (interrupted by signal 11: SIGSEGV)
C++ 协程、coroutine_handles、co_yield 等是否是 PyBind11 尚不支持的低级事物?
即使 PyBind11
不直接支持协程,您的问题也不会混合协程和 pybind 代码,因为无论如何您都将协程隐藏在 Gen
后面。
问题是您的 Generator2
类型使用编译器提供的复制和移动构造函数。
这一行:
myCoroutineResult = myCoroutineFunction();
当你调用myCoroutineFunction
时创建一个协程句柄,并把它放在右边的临时Generator2
中。然后,从右侧生成器初始化 myCoroutineResult
。一切都很好,但是临时的被破坏了。您的析构函数检查句柄是否有效:
~Generator2() {
if(coro)
coro.destroy();
}
但是在您的实现中,成员生成器的 coro
成员是从临时文件中复制的,而不重置临时文件的 coro
成员。因此,一旦您初始化 myCoroutineResult
,协程本身就会被销毁,并且您持有一个悬空的协程句柄。请记住,std::coroutine_handle
s 的行为类似于原始指针。
本质上,您违反了规则 5。您有自定义析构函数,但没有 copy/move 构造函数或赋值运算符。由于你不能复制构造协程,你可以忽略复制构造函数但你需要提供 move constructors/assigment operators:
Generator2(Generator2&& rhs) : coro{std::exchange(rhs.coro, nullptr)} {
// rhs will not delete our coroutine,
// since we put nullptr to its coro member
}
Generator2& operator=(Generator2&& rhs) {
if (&rhs == this) {
return *this;
}
if (coro) {
coro.destroy();
}
coro = std::exchange(rhs.coro, nullptr);
return *this;
}
此外,使用成员初始化列表来初始化成员,而不是在构造函数体内分配它们。所以不是这个:
Gen(){
myCoroutineResult = myCoroutineFunction();
}
使用这个:
Gen() : myCoroutineResult{myCoroutineFunction()} {}
即使在这个答案中也能看出推理。第一个调用赋值运算符,它执行大量额外的工作,而第二个调用移动构造函数,它尽可能精简。
我正在尝试使用 PyBind11 获得一个简单的基于 C++ 20 的生成器模式。这是代码:
#include <pybind11/pybind11.h>
#include <coroutine>
#include <iostream>
struct Generator2 {
Generator2(){}
struct Promise;
using promise_type=Promise;
std::coroutine_handle<Promise> coro;
Generator2(std::coroutine_handle<Promise> h): coro(h) {}
~Generator2() {
if(coro)
coro.destroy();
}
int value() {
return coro.promise().val;
}
bool next() {
std::cout<<"calling coro.resume()";
coro.resume();
std::cout<<"coro.resume() called";
return !coro.done();
}
struct Promise {
void unhandled_exception() {std::rethrow_exception(std::move(std::current_exception()));}
int val;
Generator2 get_return_object() {
return Generator2{std::coroutine_handle<Promise>::from_promise(*this)};
}
std::suspend_always initial_suspend() {
return {};
}
std::suspend_always yield_value(int x) {
val=x;
return {};
}
std::suspend_never return_void() {
return {};
}
std::suspend_always final_suspend() noexcept {
return {};
}
};
};
Generator2 myCoroutineFunction() {
for(int i = 0; i < 100; ++i) {
co_yield i;
}
}
class Gen{
private:
Generator2 myCoroutineResult;
public:
Gen(){
myCoroutineResult = myCoroutineFunction();
}
int next(){
return (myCoroutineResult.next());
}
};
PYBIND11_MODULE(cmake_example, m) {
pybind11::class_<Gen>(m, "Gen")
.def(pybind11::init())
.def("next", &Gen::next);
}
但是我收到一个错误:
Process finished with exit code 139 (interrupted by signal 11: SIGSEGV)
C++ 协程、coroutine_handles、co_yield 等是否是 PyBind11 尚不支持的低级事物?
即使 PyBind11
不直接支持协程,您的问题也不会混合协程和 pybind 代码,因为无论如何您都将协程隐藏在 Gen
后面。
问题是您的 Generator2
类型使用编译器提供的复制和移动构造函数。
这一行:
myCoroutineResult = myCoroutineFunction();
当你调用myCoroutineFunction
时创建一个协程句柄,并把它放在右边的临时Generator2
中。然后,从右侧生成器初始化 myCoroutineResult
。一切都很好,但是临时的被破坏了。您的析构函数检查句柄是否有效:
~Generator2() {
if(coro)
coro.destroy();
}
但是在您的实现中,成员生成器的 coro
成员是从临时文件中复制的,而不重置临时文件的 coro
成员。因此,一旦您初始化 myCoroutineResult
,协程本身就会被销毁,并且您持有一个悬空的协程句柄。请记住,std::coroutine_handle
s 的行为类似于原始指针。
本质上,您违反了规则 5。您有自定义析构函数,但没有 copy/move 构造函数或赋值运算符。由于你不能复制构造协程,你可以忽略复制构造函数但你需要提供 move constructors/assigment operators:
Generator2(Generator2&& rhs) : coro{std::exchange(rhs.coro, nullptr)} {
// rhs will not delete our coroutine,
// since we put nullptr to its coro member
}
Generator2& operator=(Generator2&& rhs) {
if (&rhs == this) {
return *this;
}
if (coro) {
coro.destroy();
}
coro = std::exchange(rhs.coro, nullptr);
return *this;
}
此外,使用成员初始化列表来初始化成员,而不是在构造函数体内分配它们。所以不是这个:
Gen(){
myCoroutineResult = myCoroutineFunction();
}
使用这个:
Gen() : myCoroutineResult{myCoroutineFunction()} {}
即使在这个答案中也能看出推理。第一个调用赋值运算符,它执行大量额外的工作,而第二个调用移动构造函数,它尽可能精简。