C++ Coroutine

0.392字数 2423阅读 4050

作用、原理和应用

作用

协程的作用是在同一个线程中通过保存代码执行段状态,进行代码段的分次执行,以及多个代码段的交织执行。每个代码段可以成为一个协程。

本质上讲Coroutine是一种特殊类型的subroutine,它可以在执行过程中多次暂停(yield)、然后过一段时间又可以重新从暂停的地方开始执行(resume),在重新执行时subroutine的上下文(局部变量)是保持的。function object也可以有类似的保存上下文的能力,coroutine不同的地方在于每次是从上次暂停的地方开始继续执行,而function object每次是从头开始执行。《微信服务器协程原理解析及优化

协程是在线程之上由“用户”构建的并发单元,对OS来说无感知,协程的切换由用户自己管理和调度。(这里的用户是相较于内核而言的,一些通用库这里也理解为用户)《谈谈对协程的理解

协程的好处是避免了线程之间的频换切换、以及线程安全的优化(对于同一个变量的访问无需做同步控制)。
协程极大的优化了程序员的编程体验,同步编程风格能快速构建模块,并易于复用,而且有异步的性能(这个看具体库的实现),也不用陷入callback hell的深坑。

协程的好处有哪些vczh的回复,把C#的async/await与协程的概念关联起来,厉害。

协程到底有没有性能提升?

历史上现有协程,再有线程。OS利用协程来模拟多任务并发,但由于是非抢占式的,导致多任务无法公平共享,所以被抢占式的线程替代。说协程性能好的,其实真正的原因是因为瓶颈在IO上面,而这个时候真正发挥不了线程的作用。《协程的好处有哪些?

原理

原理是将协程所用的堆栈信息保存起来,下一次使用时再装载,以时间换空间。tishion说,试想操作系统中有500个任务,而系统只有固定的255个tss段,怎么办?分时复用,当前被调度的任务可以获得一个TSS段,然后把自己的TSS内容拷贝到那个TSS段,等到这个任务的时间用完,就把自己的TSS拷贝到一边保存起来。下一个被调度的再拷贝进来。就是一个碗10个人用来吃饭。就是一个碗10个人用来吃饭。Windows和Linux的任务调度都是这个思想和实现。「共享栈」也可以叫作 Copy Stack ,意思是栈空间是所有的协程共享的,在切换的时候通过把协程栈的内容copy-in/copy-out来实现栈的切换。

协程的实现需要wrapper around native APIs for coroutines like fibers on Windows and ucontext.h on POSIX systems, falling back to a setjmp/longjmp-based implementation on other platforms. Improving a large C++ project with coroutines

应用

There’s a lot of code that can be simplified using coroutines, Improving a large C++ project with coroutines

微信的后台也是利用协程进行了优化和代码简化。

现有的Coroutine libraries

boost.coroutine

boost.asio对boost.coroutine库有个封装,

boost.coroutine是stackfull的。stackless的coroutine不能在reenter中使用局部变量,即使加个大括号可以使用局部变量,再次进入函数的时候变量的值也会丢失,而stackfull的则不会。这就带来了很大的方便。
yyzybb537/libgo(Go-style concurrency in C++11)也实现了一个coroutine,作者有详细的性能测试对比,而且说:

1.asio的coroutine会被网络IO阻塞,虽然使用者可以使用异步网络io,但是第三方库并不会,因此现有的使用同步网络IO的第三方库统统不能用。
2.asio的coroutine只是简单的网络层面的协程,缺失很多feature,比如channel mutex等,仅仅是个协程模型的网络库. 这个corotuine却是一个完整的协程模型的框架,特性完整.

libgo coroutine

libgo是魅族公司内部的一个项目。libgo是一个使用C++11编写的协作式调度的stackful协程库,同时也是一个强大的并行编程库, 是专为Linux服务端程序开发设计的底层框架,不过目前也支持Windows。libgo依赖于boost。
知乎上如何评价c++的协程库libgo?中作者的回答
作为libgo的作者,好坏不便于评价,至少我们项目组用的都挺爽的~在此展示一些设计理念和测试结果吧.
Q: 更重要的是hook了阻塞的api,使得可以与第三方的同步网络库无缝兼容,不知道有没有坑?A: libgo在hook的时候,会让api的行为与系统原生api完全保持一致,每一个边界条件的返回值和错误码都是一致的。在魅族推送系统线上的100多台服务器中经过了半年左右的考验,目前最新的2.6版本已经非常稳定了。
Q: 见过不少协程的轮子,但从没见过支持多线程调度的,我觉得这个特性很有竞争力,就是不知道调度性能如何?A: 多线程调度使用的是worksteal算法,性能上的损耗很小,远远快于golang。
以下是libgo协程切换速度和管道读写速度与golang对比测试的结果:------------- libgo ---------------BenchmarkSwitch_1 1000000 154 ns/opBenchmarkSwitch_1000 1000000 105 ns/opBenchmarkSwitch_10000 1000000 553 ns/opBenchmarkChannel_0 1000000 485 ns/opBenchmarkChannel_1 1000000 299 ns/opBenchmarkChannel_10000 10000 108 ns/opBenchmarkChannel_5000000 5000000 113 ns/op--------------------------------------------------- golang --------------BenchmarkSwitch_1 1000000 1438 ns/opBenchmarkSwitch_1000 1000000 1654 ns/opBenchmarkSwitch_10000 500000 2375 ns/opBenchmarkChannel_0 1000000 1536 ns/opBenchmarkChannel_1 500000 3037 ns/opBenchmarkChannel_N 50000000 65.0 ns/op--------------------------------------
测试代码在libgo/test/golang目录中,直接执行test.sh脚本即可得到结果.
下面有热心用户给出的另一组结果截然不同的测试数据,在此补充一点,libgo使用boost1.59以上的版本才可以获得这样的高性能,cmake命令执行参数:$ cmake .. -DENABLE_BOOST_CONTEXT=ON

另外, 很重要的一点. 在实际使用中, 我们基于libgo封装了一个网络库(libgonet)作为我们的base框架.libgonet也是个开源项目, 希望在linux系统上基于libgo开发网络程序的, 可以关注一下哦~
编辑于2016-10-21

state-threads

这是某些人认为很好的协程库。

The State Threads Library is a small application library which provides
a foundation for writing fast and highly scalable Internet applications
(such as web servers, proxy servers, mail transfer agents, and so on,
really any network-data-driven application) on UNIX-like platforms. It
combines the simplicity of the multithreaded programming paradigm, in
which one thread supports each simultaneous connection, with the
performance and scalability of an event-driven state machine
architecture. In other words, this library offers a threading API for
structuring an Internet application as a state machine. For more
details, please see the library documentation in the "docs" directory or
on-line at

http://state-threads.sourceforge.net/docs/

The State Threads Project is an open source project for maintaining and
enhancing the State Threads Library. For more information about this
project, please see

http://state-threads.sourceforge.net/

Coroutines in LLVM

LLVM的协程,还没来得及看。

zyzacz的cpp协程代码

Yuandong-Chen/coroutine

RethinkDB 核心所用的协程

libcoroutine
Libcoroutine is a thin, cross-platform wrapper around native APIs for coroutines like fibers on Windows and ucontext.h on POSIX systems, falling back to a setjmp/longjmp-based implementation on other platforms.

Improving a large C++ project with coroutines
中说:

At the core of RethinkDB is a highly parallel B-tree implementation. Due to our performance requirements, it is too expensive to create a native thread for each request. Instead, we create one thread per CPU on the server (logical CPU in the case of hyperthreading) and use cooperative concurrency within a thread.

A single thread will have multiple logically concurrent units of control, taking turns when a unit needs to block. Blocking needs to take place ultimately for either I/O–waiting for information from the network or disk, or waiting to be notified that sending information there has completed–or for coordination with other threads. On top of this, we implemented higher-level abstractions which also block.

具体实现和注意事项

注意事项:

由于协程的切换是由程序员自己调度的,所以很难说协程切换的代价比省去的线程切换代价小,合理的方式应该是通过测试工具在具体的业务场景得出一个最好的平衡点。《谈谈对协程的理解

There will also be some work to ensure that coroutines don’t cause performance regressions.Improving a large C++ project with coroutines

知识点小贴士

=default=delete

=default is a new C++11 feature.
It means that you want to use the compiler-generated version of that function, so you don't need to specify a body.
You can also use = delete
to specify that you don't want the compiler to generate that function automatically.
With the introduction of move constructors and move assignment operators, the rules for when automatic versions of constructors, destructors and assignment operators are generated has become quite complex. Using = default and = delete makes things easier as you don't need to remember the rules: you just say what you want to happen.
= delete is stronger: It means, using that function is forbidden, though it still takes part in overload resolution.

用不到的

Boost Fusion,heterogeneous containers,不知道性能如何,应该用不到。

杂项

libgo这个项目的make系统可以自动生成C++项目,很不错,值得参考。

参考

[1] boost.coroutine学习笔记

推荐阅读更多精彩内容