C ++ 1z协程线程上下文和协同调度

时间:2017-01-01 05:05:35

标签: c++ multithreading async-await coroutine c++-coroutine

根据这个最新的C ++ TS:http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/n4628.pdf,基于对C#async / await语言支持的理解,我想知道C ++协同程序的“执行上下文”(借用C#的术语)是什么?

我在Visual C ++ 2017 RC中的简单测试代码表明,协同程序似乎总是在线程池线程上执行,并且对应用程序开发人员的控制很少,在该程序开发人员上可以执行协同程序的线程上下文 - 例如一个应用程序是否可以强制所有协程(使用编译器生成的状态机代码)仅在主线程上执行,没有涉及任何线程池线程?

在C#中,SynchronizationContext是一种指定“上下文”的方法,其中所有协程“一半”(编译器生成的状态机代码)将被发布并执行,如本文所示:https://blogs.msdn.microsoft.com/pfxteam/2012/01/20/await-synchronizationcontext-and-console-apps/,而Visual C ++ 2017中的当前协程实现RC似乎总是依赖于并发运行时,它默认在线程池线程上执行生成的状态机代码。是否有类似的同步上下文概念,用户应用程序可以使用它来将协同程序执行绑定到特定的线程?

此外,在Visual C ++ 2017 RC中实现的协同程序的当前默认“调度程序”行为是什么?即1)如何准确指定等待条件? 2)当满足等待条件时,谁调用暂停的协程的“下半部分”?

关于C#中任务调度的我(天真)猜测是C#“完全”通过任务继续“实现”等待条件 - 等待条件由TaskCompletionSource拥有的任务合成,任何需要等待的代码逻辑将被链接为延续它,所以如果满足等待条件,例如如果从低级网络处理程序接收到完整消息,它会执行TaskCompletionSource.SetValue,它将基础任务转换为已完成状态,从而有效地允许链式连续逻辑开始执行(将任务置于就绪状态/列表中在C ++协程中,我推测std :: future和std :: promise将被用作类似的机制(std :: future是任务,而std :: promise是TaskCompletionSource,用法也令人惊讶地相似!) - C ++协同调度程序(如果有的话)也依赖于某种类似的机制来执行这种行为吗?

[编辑]:在做了一些进一步的研究后,我能够编写一个非常简单但非常强大的抽象代码,称为awaitable,支持单线程和协作式多任务处理,并具有一个简单的基于thread_local的调度程序,可以在线程上执行协同程序启动 root 协同程序。代码可以在这个github repo中找到:https://github.com/llint/Awaitable

Awaitable是一种可组合的方式,它在嵌套级别维护正确的调用排序,并且它具有原始的让步,定时等待和从其他地方设置就绪,并且可以从中导出非常复杂的使用模式(例如无限循环)只有在某些事件发生时才被唤醒的协程,编程模型紧跟C#Task async / await模式。请随时提供反馈。

1 个答案:

答案 0 :(得分:11)

相反!

C ++协程是关于控制的。这里的关键点是void await_suspend(std::experimental::coroutine_handle<> handle) 功能

evey co_await期待等待类型。简而言之,等待类型是提供这三种功能的类型:

  1. bool await_ready() - 程序是否应该停止执行协程?
  2. void await_suspend(handle) - 程序会为您传递该协程帧的延续上下文。如果你激活句柄(例如,通过调用句柄提供的operator () - 当前线程立即恢复协程)。
  3. T await_resume() - 告诉线程恢复协程,恢复协程时要做什么以及从co_await返回什么。
  4. 所以当你在等待类型上调用co_await时,程序会询问是否应该暂停协程(如果await_ready返回false),如果是这样的话 - 你会得到一个协程句柄,你可以在其中做你喜欢的事。

    例如,您可以将协程句柄传递给线程池。在这种情况下,线程池线程将恢复协程。

    你可以将协程句柄传递给一个简单的std::thread - 你的拥有创建线程将恢复协程。

    您可以将协程句柄附加到OVERLAPPED的派生类中,并在异步IO完成时恢复协程。

    正如您所看到的 - 您可以通过管理await_suspend中传递的协程句柄来控制协程暂停和恢复的位置和时间。没有“默认调度程序” - 你如何实现等待类型将决定如何对协程进行schedueled。

    那么,VC ++会发生什么?遗憾的是,std::future仍然没有then功能,因此无法将协程句柄传递给std::future。如果等待std::future - 程序将只打开一个新线程。查看future标题提供的源代码:

    template<class _Ty>
        void await_suspend(future<_Ty>& _Fut,
            experimental::coroutine_handle<> _ResumeCb)
        {   // change to .then when future gets .then
        thread _WaitingThread([&_Fut, _ResumeCb]{
            _Fut.wait();
            _ResumeCb();
        });
        _WaitingThread.detach();
        } 
    

    那么为什么在常规std::thread中启动协同程序时会看到win32线程池线程?那是因为它不是协程。 std::async在幕后调用concurrency::create_task。默认情况下,在win32线程池下启动concurrency::task。毕竟,std::async的整个目的是在另一个线程中启动callable。