简体   繁体   English

如何将事物传递给 C++20 协程 co_await await_suspend 运算符 function,例如线程池引用?

[英]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:我有这个例子,它并不十分简单,但可以完成工作:

  • Make your coroutines return a task<T> .让你的协程返回一个task<T>
task<int> async_add(int a, int b) { ... }
  • Let the task share a state with its coroutine_promise .task与其coroutine_promise共享一个state The state : state
    • is implemented as an executable, resuming the coroutine when executed, and作为可执行文件实现,在执行时恢复协程,并且
    • holds the result of the operation (eg a 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_;
};
  • The coroutine_promise returns a task_scheduler awaiter at initial_suspend : coroutine_promiseinitial_suspend返回一个task_scheduler等待者:
template <typename T>
class task<T>::coroutine_promise {
public:
    auto initial_suspend() {
        return task_scheduler<task<T>>{};
    }
  • The 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());
    }
};
  • Wrapping it all up: calls to a coroutine will make a 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();

[Demo] [演示]

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM