使用C++20协程在线程之间切换

jyztefdp  于 11个月前  发布在  其他
关注(0)|答案(3)|浏览(127)

有一个example的切换到不同的线程与C++20协程:

#include <coroutine>
#include <iostream>
#include <stdexcept>
#include <thread>

auto switch_to_new_thread(std::jthread& out) {
    struct awaitable {
        std::jthread* p_out;
        bool await_ready() { return false; }
        void await_suspend(std::coroutine_handle<> h) {
            std::jthread& out = *p_out;
            if (out.joinable())
                throw std::runtime_error("Output jthread parameter not empty");
            out = std::jthread([h] { h.resume(); });
            // Potential undefined behavior: accessing potentially destroyed *this
            // std::cout << "New thread ID: " << p_out->get_id() << '\n';
            std::cout << "New thread ID: " << out.get_id() << '\n'; // this is OK
        }
        void await_resume() {}
    };
    return awaitable{ &out };
}

struct task {
    struct promise_type {
        task get_return_object() { return {}; }
        std::suspend_never initial_suspend() { return {}; }
        std::suspend_never final_suspend() noexcept { return {}; }
        void return_void() {}
        void unhandled_exception() {}
    };
};

task resuming_on_new_thread(std::jthread& out) {
    std::cout << "Coroutine started on thread: " << std::this_thread::get_id() << '\n';
    co_await switch_to_new_thread(out);
    // awaiter destroyed here
    std::cout << "Coroutine resumed on thread: " << std::this_thread::get_id() << '\n';
}

int main() {
    std::jthread out;
    resuming_on_new_thread(out);
}

字符串
协同程序在主线程上开始并切换到新创建的线程。
什么是使它切换回主线程的正确方法?
下面的代码

task resuming_on_new_thread(std::jthread& out) {
    std::cout << "Coroutine started on thread: " << std::this_thread::get_id() << '\n';
    co_await switch_to_new_thread(out);
    // awaiter destroyed here
    std::cout << "Coroutine resumed on thread: " << std::this_thread::get_id() << '\n';
    co_await switch_to_main_thread();
    std::cout << "Coroutine resumed on thread: " << std::this_thread::get_id() << '\n';
}


会打印

Coroutine started on thread: 139972277602112
New thread ID: 139972267284224
Coroutine resumed on thread: 139972267284224
Coroutine resumed on thread: 139972277602112

kninwzqo

kninwzqo1#

switch_to_new_thread实际上 * 创建了一个新线程 *,它并没有 * 切换到一个新线程 *。然后它注入代码来恢复其中的协程。
要在一个特定的线程上运行代码,你必须在那个线程上运行代码。要恢复一个协程,那个特定的线程必须运行恢复那个协程的代码。
在这里,您通过创建一个全新的线程并注入执行resume的代码来实现这一点。
一种传统的方法是使用消息泵,你想参与的线程有一个消息泵和一个事件队列,它按顺序运行事件。
为了让特定的线程运行某些代码,您向该事件队列发送一条消息,其中包含指令(可能是实际的代码,也可能只是一个值)。
为此,这样的“事件消费线程”不仅仅是std::jthreadstd::thread;它是一个线程安全队列,线程中的一些任务从它弹出并执行它们。
在这样的系统中,你可以通过发送消息在线程之间移动。
所以你会有一个队列:

template<class T>
struct threadsafe_queue {
  [[nodiscard]] std::optional<T> pop();
  [[nodiscard]] std::deque<T> pop_many(std::optional<std::size_t> count = {}); // defaults to all
  [[nodiscard]] bool push(T);
  template<class C, class D>
  [[nodiscard]] std::optional<T> wait_until_pop(std::chrono::time_point<C,D>);
  void abort();
  [[nodiscard]] bool is_aborted() const { return aborted; }
private:
  mutable std::mutex m;
  std::condition_variable cv;
  std::deque<T> queue;
  bool aborted = false;
  auto lock() const { return std::unique_lock(m); }
};

字符串
任务:

using task_queue = threadsafe_queue<std::function<void()>>;


一个基本的消息泵是:

void message_pump( task_queue& q ) {
  while (auto f = q.pop()) {
    if (*f) (*f)();
  }
}


然后你会创建两个task_queues,一个用于你的主线程,一个用于你的工作线程。要切换到工作线程而不是创建一个新的jthread,你会:

workerq.push( [&]{ h.resume(); } );


类似地,

mainq.push( [&]{ h.resume(); } );


有很多细节我都跳过了,但这是一个草图,你会怎么做。

kt06eoxx

kt06eoxx2#

一种方法是创建一个线程安全的队列,协程将自己放入其中,告诉主线程“请立即恢复我”。此时,您基本上是在构建一个线程池。main函数必须监视该队列(定期轮询它或等待某些东西被放入其中),然后获取并执行一个可用的元素(工作项)。

ggazkfy8

ggazkfy83#

你可以通过创建一个suspend never co_routine来做到这一点,这让我可以这样做:

task<int> task1()
{  
    co_return 11;
}

task<int> task2()
{  
    co_return 12;
}

sync<int> background_chumpy()
{ 
   int a = co_await task1();
   int b = co_await task2();
   co_return a+b;
}

字符串
同步和任务是我自己的名字,但你的里程会有所不同。现在,地球上的每一个海报都在暗示,在这里,是,当你这样做时,你的整个线程将等待..在这一点上,你必须问,什么是重点,因为你已经暂停了所有的作业完成你的主线程。嗯,它让你管道task1和task2跨多个线程,这是很有用的
我正在探索的是一个co_routine,它做了它应该做的事情。你需要将消息发布到应用程序队列。在Windows中,你有一个内置到OS中的消息队列本身。所以你可以从后台线程将PostMessage发送回你自己的应用程序,并将其发送到需要的任何地方,当任务完成时。这基本上捆绑了当前的实践,即让线程在WinUI中调用调度程序。在后台,我相信,调度程序是主线程上的消息循环。
希望这对你有帮助。

相关问题