c++协程实现的原理和机制

一、协程

多线程编程对程序员来说绝对是一场噩梦,特别在大规模的高并发服务端编程中,线程池和异步IO共同工作,一旦出现异常,定位和修改,除了需要浓厚的技术功底和实践经验,有时还需要一些人品。既然多线程编程如此的不友好,能不能有什么东西做到可以让他友好一些,甚至不用多线程编程呢?你敢提出问题,就有人敢解决问题。
协程就是这样产生的,也就是既有多线程的优势,又没有多线程的复杂性(或者说复杂性大大降低)。在前边的《c++20中的协程》中,对c++20中STL库提供的无栈协程进行了初步的分析和应用。其它如libco等第三方库中也提供了类似的实现。其它的语言如Go、Python等也都提供了协程的应用。
协程之火是越烧越旺,看上去确实是很美!

二、协程和c++

c++做为一个相对古老的语言,曾经是步履蹒跚,直到c++11才奋起直追,但是对新技术的整体演进,其实c++仍然是保守的。现在c++20的标准虽然已经实现了协程,但目前能比较好支持c++20的编译器几乎都和整体的环境不太兼容。换句话说,还需要继续等待整个c++的迭代版本,可能到了c++23,整体的环境就会跟上去,协程才会真正的飞入程序员的“寻常百姓家”。
正如前面提到的,协程一般来说是不需要锁的,但是如果协程的底层操作是跨越线程动态操作,仍然是需要锁的存在的。这也是为什么要求尽量把协和的调度放到一个线程中去的原因。

三、协程的原理

既然协程如此厉害,那么它实现的原理到底是什么呢?协程最重要的应用方式就是把线程在内核上的开销转到了应用层的开销,避开或者屏蔽(对应用者)线程操作的难度。那多线程操作的复杂性在哪儿呢?线程切换的随机性和线程Context的跟随,出入栈的保存和恢复,相关数据的锁和读写控制。这才是多线程的复杂性,如果再加异步引起的数据的非连续性和事件的非必然性操作,就更加增强了多线程遇到问题的判别和断点的准确。
好,既然是这样,那么上框架,封装不就得了。
协程和线程一样,同样需要做好两个重点:第一个是协程的调度;第二是上下文的切换。而这两点在OS的相关书籍中的介绍海了去了,这里就不再赘述,原理基本都是一样的。
如果以协程的关系来区分,协程也可以划分为对称和非对称协程两种。协程间是平等关系的,就是对称的;反之为非对称的。名字越起越多,但事儿还是那么两下子,大家自己体会即可。
只要能保证上面所说的对上下文数据的安全性保证又能够实现协程在具体线程上的操作(某一个线程上执行的所有协程是串行的),那么锁的操作,从理论上讲是不需要的(但实际开发中,因为协程的应用还是少,所以还需要具体的问题具体分析)。协程的动作集中在应用层,而把复杂的内核调度的线程屏蔽在下层框架上(或者以后会不会出现OS进行封装),从而大幅的降低了编程的难度,但却拥有了线程快速异步调用的效果。

四、协程实现机制

协程的实现有以下几种机制:
1、基于汇编的实现
这个对汇编编程得要求有两下子,这个网上也有不少例子,就不再这里搬门弄斧了。

2、基于switch-case来实现
这个其实更像是一个C语言的技巧,利用不同的状态Case来达到目的,或者说大家认知中的对编程语言的一种内卷使用,网上有一个开源的项目:
https://github.com/georgeredinger/protothreads
3、基于操作系统提供的接口:Linux的ucontext,Windows的Fiber
Fiber可能很多人都不熟悉,这其实就是微软原来提供的纤程,有兴趣的可以去网上查找一下,有几年这个概念炒得还是比较火的。ucontext是Linux上的一种操作,这两个都可以当作是一种类似特殊的应用存在。游戏界的大佬云风(《游戏之旅:我的编程感悟》作者)的coroutine就是类似于这种。兴趣是编程的动力,大家如果对这些有兴趣可以看看这本书,虽然其中很多的东西都比较老了,但是整体的思想还是非常有借鉴的。

4、基于接口 setjmp 和 longjmp同时使用 static local 的变量来保存协程内部的数据
这两个函数是C语言的一个非常有意思的应用,一般写C好长时间的人,都没接触过这两个API函数,这个函数的定义是:

int setjmp(jmp_buf envbuf);
void longjmp(jmp_buf envbuf, int val);

它们两个的作用,前者是用来将栈桢(上下文)保存在jmp_buf这个数据结构中,然后可以通过后者 longjmp在指定的位置恢复出来。这就类似于使用goto语句跳转到任意的地方,然后再把相关的数据恢复出来。看一下个《C专家编程》中的例子:

#include 
#include 

jmp_buf buf;

banana()
{
    printf("in banana() \n");
    longjmp(buf,1);
    printf("you'll never see this,because i longjmp'd");
}

main()
{
    if(setjmp(buf))
        printf("back in main\n");
    else
    {
        printf("first time through\n");
        banana();
    }
}

看完了上述的几种方法,其实网上还有几种实现的方式,但都是比较刻板,有兴趣的可以搜索一下,这里就不提供链接了。
协程的实现,按理说还是OS搞定最好,其实是框架底层,但C/C++的复杂性,以及不同的平台和不同编译器、库之间的长期差异,导致这方面能做好的可能性真心是觉得不会太大。

五、总结

协程的出现其实就是迎合语言的简单化、易用化。不可否认,一个语言如果长期处在曲高和寡的地位,会不会被淘汰,不言而喻。但是,一味的向简单易用推进,会不会是另外一种问题的极端化倾向。整体上,科技以人为本,编程普及化,确实是一个应该推进的方向。包括现在的低代码编程也是这种潮流的一种外在表现。
是与非,对与错,就留待时间来判断吧。
c++协程实现的原理和机制_第1张图片

你可能感兴趣的:(C++11,C++,c++)