[英]How can I pass things to a C++20 coroutine co_await await_suspend operator function such as a thread pool reference?
I am trying to work with Coroutines and multithreading together in C++.我正在尝试在 C++ 中使用协程和多线程。
In many coroutine examples, they create a new thread in the await_suspend
of the co_await
operator for the promise type.在许多协程示例中,它们在 promise 类型的co_await
运算符的await_suspend
中创建了一个新线程。 I want to submit to a thread pool in this function.我想提交到这个function中的一个线程池。
Here I define a co_await
for future<int>
.这里我为future<int>
定义了一个co_await
。
void await_suspend(std::coroutine_handle<> handle) {
this->wait();
handle.resume();
}
I want to change this code to submit a lambda/function pointer to a threadpool.我想更改此代码以将 lambda/函数指针提交给线程池。 Potentially I can use Alexander Krizhanovsky's ringbuffer to communicate with the threadpool to create a threadpool by myself or use boost's threadpool.潜在地我可以使用 Alexander Krizhanovsky 的 ringbuffer 与线程池通信以自己创建线程池或使用 boost 的线程池。
My problem is NOT the thread pool.我的问题不是线程池。 My problem is that I don't know how to get reference to the threadpool in this co_await
operator.我的问题是我不知道如何在此co_await
运算符中获取对线程池的引用。
How do I pass data from the outside environment where the operator is to this await_suspend
function?如何将数据从操作员所在的外部环境传递到此await_suspend
function? Here is an example of what I want to do:这是我想做的一个例子:
void await_suspend(std::coroutine_handle<> handle) {
// how do I get "pool"? from within this function
auto res = pool.enqueue([](int x) {
this->wait();
handle.resume();
});
}
I am not an expert at C++ so I'm not sure how I would get access to pool
in this operator?我不是 C++ 的专家,所以我不确定我将如何访问这个运营商的pool
?
Here's the full code inspired by this GitHub gist A simple C++ coroutine example .这是受此 GitHub 要点启发的完整代码一个简单的 C++ 协程示例。
#include <future>
#include <iostream>
#include <coroutine>
#include <type_traits>
#include <list>
#include <thread>
using namespace std;
template <>
struct std::coroutine_traits<std::future<int>> {
struct promise_type : std::promise<int> {
future<int> get_return_object() { return this->get_future(); }
std::suspend_never initial_suspend() noexcept { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
void return_value(int value) { this->set_value(value); }
void unhandled_exception() {
this->set_exception(std::current_exception());
}
};
};
template <>
struct std::coroutine_traits<std::future<int>, int> {
struct promise_type : std::promise<int> {
future<int> get_return_object() { return this->get_future(); }
std::suspend_never initial_suspend() noexcept { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
void return_value(int value) { this->set_value(value); }
void unhandled_exception() {
this->set_exception(std::current_exception());
}
};
};
auto operator co_await(std::future<int> future) {
struct awaiter : std::future<int> {
bool await_ready() { return false; } // suspend always
void await_suspend(std::coroutine_handle<> handle) {
this->wait();
handle.resume();
}
int await_resume() { return this->get(); }
};
return awaiter{std::move(future)};
}
future<int> async_add(int a, int b)
{
auto fut = std::async([=]() {
int c = a + b;
return c;
});
return fut;
}
future<int> async_fib(int n)
{
if (n <= 2)
co_return 1;
int a = 1;
int b = 1;
// iterate computing fib(n)
for (int i = 0; i < n - 2; ++i)
{
int c = co_await async_add(a, b);
a = b;
b = c;
}
co_return b;
}
future<int> test_async_fib()
{
for (int i = 1; i < 10; ++i)
{
int ret = co_await async_fib(i);
cout << "async_fib(" << i << ") returns " << ret << endl;
}
}
int runfib(int arg) {
auto fut = test_async_fib();
fut.wait();
return 0;
}
int run_thread() {
printf("Running thread");
return 0;
}
int main()
{
std::list<shared_ptr<std::thread>> threads = { };
for (int i = 0 ; i < 10; i++) {
printf("Creating thread\n");
std::shared_ptr<std::thread> thread = std::make_shared<std::thread>(runfib, 5);
threads.push_back(thread);
}
std::list<shared_ptr<std::thread>>::iterator it;
for (it = threads.begin(); it != threads.end(); it++) {
(*it).get()->join();
printf("Joining thread");
}
fflush(stdout);
return 0;
}
You could have a thread pool, and let the coroutine promise to schedule work on it.你可以有一个线程池,让协程 promise 来安排它的工作。
I have this example around that is not exactly simple but may do the work:我有这个例子,它并不十分简单,但可以完成工作:
task<T>
.让你的协程返回一个task<T>
。task<int> async_add(int a, int b) { ... }
task
share a state
with its coroutine_promise
.让task
与其coroutine_promise
共享一个state
。 The state
: state
:
std::promise<T>
).保存操作的结果(例如std::promise<T>
)。template <typename T>
class task<T>::state : public executable {
public:
void execute() noexcept override {
handle_.resume();
}
...
private:
handle_type handle_;
std::promise<T> result_;
};
coroutine_promise
returns a task_scheduler
awaiter at initial_suspend
: coroutine_promise
在initial_suspend
返回一个task_scheduler
等待者:template <typename T>
class task<T>::coroutine_promise {
public:
auto initial_suspend() {
return task_scheduler<task<T>>{};
}
task_scheduler
awaiter schedules the state
: task_scheduler
服务员安排state
:template <is_task task_t>
struct task_scheduler : public std::suspend_always {
void await_suspend(task_t::handle_type handle) const noexcept {
thread_pool::get_instance().schedule(handle.promise().get_state());
}
};
state
be scheduled on a thread, and, whenever a thread executes that state
, the coroutine will be resumed.总结一下:对协程的调用将使一个state
被安排在一个线程上,并且,每当一个线程执行该state
时,协程将被恢复。 The caller can then wait for the task's result.然后调用者可以等待任务的结果。auto c{ async_add(a,b) };
b = c.get_result();
That example is from 2018, and was built for the Coroutine TS.该示例来自 2018 年,是为协程 TS 构建的。 So it's missing a lot of stuff from the actual C++20 feature.所以它从实际的 C++20 特性中遗漏了很多东西。 It also assumes the presence of a lot of things that didn't make it into C++20.它还假设存在很多没有进入 C++20 的东西。 The most notable of which being the idea that std::future
is an awaitable type, and that it has continuation support when coupled with std::async
.其中最值得注意的是std::future
是可等待类型的想法,并且在与std::async
结合时它具有持续支持。
It's not, and it doesn't.不是,也不是。 So there's not much you can really learn from this example.因此,您可以从这个示例中真正学到的东西并不多。
co_await
is ultimately built on the ability to suspend execution of a function and schedule its resumption after some value has been successfully computed. co_await
最终建立在暂停执行 function 并在成功计算某个值后安排其恢复的能力之上。 The actual C++20 std::future
has exactly none of the machinery needed to do that.实际的 C++20 std::future
完全没有执行此操作所需的任何机制。 Nor does std::asyc
give it the ability to do so. std::asyc
也没有赋予它这样做的能力。
As such, neither is an appropriate tool for this task.因此,两者都不是完成此任务的合适工具。
You need to build your own future type (possibly using std::promise/future
internally) which has a reference to your thread pool.您需要构建自己的未来类型(可能在内部使用std::promise/future
),它引用了您的线程池。 When you co_await
on this future, it is that new future which passes off the coroutine_handle
to the thread pool, doing whatever is needed to ensure that this handle does not get executed until its current set of tasks is done.当你co_await
这个 future 时,新的 future 将coroutine_handle
传递给线程池,做任何必要的事情来确保这个 handle 在其当前任务集完成之前不会被执行。
Your pool or whatever needs to have a queue of tasks, such that it can insert new ones to be processed after all of the current one, and remove tasks once they've finished (as well as starting the next one).您的池或任何需要有任务队列的东西,这样它就可以在所有当前任务之后插入要处理的新任务,并在任务完成后删除任务(以及开始下一个任务)。 And those operations need to be properly synchronized.这些操作需要正确同步。 This queue needs to be accessible by both the future type and your coroutine's promise type.未来类型和协程的 promise 类型都需要可以访问此队列。
When a coroutine ends, the promise needs to tell the queue that its current task is over and to move to the next one, or suspend the thread if there is no next one.当一个协程结束时,promise 需要告诉队列它当前的任务已经结束,并移动到下一个,或者如果没有下一个则挂起线程。 And the promise's value needs to be forwarded to the next task.并且需要将 promise 的值转发给下一个任务。 When a coroutine co_await
s on a future from your system, it needs to add that handle to the queue of tasks to be performed, possibly starting up the thread again.当协程co_await
从您的系统中获取 future 时,它需要将该句柄添加到要执行的任务队列中,可能会再次启动线程。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.