基于纤程(Fiber)实现C++异步编程库(一):原理及示例

纤程(Fiber)和协程(coroutine)是差不多的概念,也叫做用户级线程或者轻线程之类的。Windows系统提供了一组API用户创建和使用纤程,本文中的库就是基于这组API实现的,所以无法跨平台使用,非Windows程序员可以闪人了,当然如果有兴趣可以继续看下去,找个第三方的协程库封装一下,也能实现相同的效果。关于纤程更详细的信息可以查阅MSDN

纤程的概念中有两个关键点:

  1. 纤程拥有独立的栈空间和寄存器环境
  2. 纤程在用户态实现调调度,也就是说完全由程序员控制;

下图演示了几个纤程相互切换的过程,注意每个纤程都有独立的栈,并且通过SwitchToFiber函数切换到其他纤程:

 

作为对比,我们可以看一下函数调用过程中的堆栈变化情况,下面是示意图,表示了func1 -> func2 -> func3 这种常见的函数嵌套调用关系:

 

 

每一次函数调用都会创建一个新的栈帧(stack frame),合起来就构成整个调用栈,函数返回时其栈帧也随之释放。对于函数调用,我们可以确定的一点是(在不抛出异常的情况下)被调用函数执行完毕后一定会在调用点返回并继续执行下一条语句。但纤程之间的调用(切换)却不同,一个纤程可以在任意位置切换到其他纤程,并且可能永远都不会再切换回来,也可能从其他任意纤程(不必是刚刚切换到的)切换回来,前面的示意图描述的只是一种非常简单的情况,实际的情况可能非常复杂,复杂到导出都是跳来跳去的箭头理也理不清。在纤程间切换,有点像用加强版的goto,用的时候固然很爽,但后续的维护却是个麻烦。

所以就像用while/for/switch-case代替goto一样,我们也需要封装一组新的API来代替对操作系统API的直接调用。一方面,在封装过程中我们可以对纤程的行为(实际是程序员的行为)施加一些安全约束,使得更容易写出安全的代码或者更不容易写出不安全的代码;另一方面,从goto到while/switch等过程控制语句实际上是一种抽象层次的提升,对大部分常见需求后者用起来更方便,更不容易出错,写出的代码也更简洁易懂,类似的,从系统API到新的封装API或者封装类也是抽象层次的提高,可以更方便的应用在各种业务场景;最后,直接使用系统API需要写很多维护纤程的辅助代码,这类代码通常重复而又分散到业务代码的各个角落,进一步降低了程序的可读性和提高了维护难度,封装也是为了解决这个问题。

好了,废话说完了,我们先上一段代码尝尝鲜:

 1     const int RUN_TIMES = 5;
 2 
 3     int number = 0;
 4     bool shutdown = false;
 5 
 6     Fiber fib([&number, &shutdown]
 7     {
 8         while (!shutdown)
 9         {
10             number++;
11             Fiber::yield();             // A:控制权移交到主纤程
12         }
13 }); 14 15 for (int i = 0; i < RUN_TIMES; i++) 16 { 17 fib.resume(); // B: 切换到子纤程执行 18 } 19 20 printf("number = %d\r\n", number);

这里先创建了一个纤程实现number变量累加的功能,然后在for循环中执行(姑且用这个词)最终得到正确的结果。AB两处代码分别实现了纤程的切换,实际上是封装了对SwitchToFiber的调用,注意两个函数调用细节上的不同:resume表示切换到对象包装的纤程,是普通成员函数,yield表示控制权移交给调用者纤程,是静态成员函数,大家可以思考下为什么有静态和非静态成员函数的差别。

下面是用纤程实现生产者-消费者模型的代码:

 1     int product_count = 0;
 2     bool is_end_time = false;
 3 
 4     const int RUN_TIMES = 3;
 5 
 6     // 生产者纤程
 7     Fiber fib_producer([&is_end_time, &product_count]
 8     {
 9         srand((unsigned)time(NULL));
10 
11         while (!is_end_time)
12         {
13             int new_product_count = (int)((double)rand() / RAND_MAX * 10) + 1;
14             product_count += new_product_count;
15 
16             printf("[producer] create new products: %d\r\n", new_product_count);
17 
18             Fiber::yield();
19         }
20 
21         printf("[producer] off duty.\r\n");
22     });
23 
24     // 消费者纤程的执行函数
25     auto consumer_proc = [&is_end_time, &product_count](const int seq_number)
26     {
27         int total_count = 0;
28 
29         while (!is_end_time)
30         {
31             if (product_count > 0)
32             {
33                 product_count--;
34                 total_count++;
35                 printf("[consumer %d] got 1 product, total got %d, remain %d\r\n", seq_number, total_count, product_count);
36             }
37 
38             Fiber::yield();
39         }
40 
41         printf("[consumer %d] off duty.\r\n", seq_number);
42     };
43 
44     const int CONSUMER_COUNT = 3;
45     int consumer_seq_number = 0;
46 
47     // 创建消费者纤程数组
48     std::vector<Fiber> consumer_array(CONSUMER_COUNT);
49     std::for_each(consumer_array.begin(), consumer_array.end(), [&](Fiber& item){ item = Fiber([&]{ consumer_proc(consumer_seq_number); }); consumer_seq_number++; });
50 
51     consumer_seq_number = 0;
52 
53     for (int i = 0; i < RUN_TIMES; i++)
54     {
55         fib_producer.resume();
56 
57         while (product_count > 0)
58         {
59             consumer_array[consumer_seq_number].resume();
60             consumer_seq_number = (consumer_seq_number + 1) % CONSUMER_COUNT;
61         }
62     }
63 
64     is_end_time = true;
65 
66     // 等待纤程结束
67     Fiber::await_all(consumer_array);
68     Fiber::await(fib_producer);

程序末尾出现了await和await_all两个新的方法可以先不用管,不影响主要逻辑。由于所有纤程都是在同一个线程中运行的所以无需加锁,这也是使用纤程的一个重要好处,也是我们这个封装库的主要目的之一。

 

限于篇幅,这次就只写这么多了,更多的内容将放到后面的帖子中,总计还要写四、五篇的样子。但代码实际上已经写完了,急性子的园友可以直接到这个地址看代码:

https://code.csdn.net/xrunning/fiber

 

建了一个QQ群:微观架构设计165241092,主要讨论C++代码级设计,感兴趣的园友加进来一起讨论学习。 

转载于:https://www.cnblogs.com/xrunning/p/4176331.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/273476.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C语言 将整数写入内存指定的连续字节单元中

将整数数组写入0x40003000开始的连续10个字节内存单元中&#xff0c;注意unsigned char *指向一个字节&#xff0c;而int *指向1个字&#xff08;4个字&#xff09;&#xff0c;但是可以把字中存储的整数放入字节单元中&#xff0c;只要不超过表示的范围&#xff0c;注意虽然un…

多项目开发下的dll文件管理

阅读目录&#xff1a;DS01&#xff1a;为什么要对生成的dll文件进行管理&#xff1f;DS02&#xff1a;首先介绍以下两个DOS命令DS03&#xff1a;第一种实现方法&#xff08;xcopy&#xff09;DS04&#xff1a;第二种实现方法&#xff08;attrib&#xff09;DS05&#xff1a;分享…

自然连接(NATURAL JOIN)

自然连接&#xff08;NATURAL JOIN&#xff09;是一种特殊的等价连接&#xff0c;它将表中具有相同名称的列自动进行记录匹配。自然连接不必指定任何同等连接条件。图9.9给出了典型的自然连接示意图。 图9.9 自然连接 自然连接自动判断相同名称的列&#xff0c;而后形成匹配。…

自连接

9.3 表的连接类型 9.3.1 自连接 自连接是指表与其自身进行连接&#xff0c;这就需要用到前面介绍的表别名。下面通过一个具体实例来讲解自连接的应用。 实例5 自连接的使用方法 查询成绩中存在不及格课程的学生的姓名、所在系、所有的课程及成绩信息。如果采用前面介绍的…

LIKE运算符

6.5 使用LIKE进行模糊查询 当只知道部分字符串时&#xff0c;可使用LIKE运算符来查询数据库&#xff0c;找出与其相关的整个字符串。因此&#xff0c;当把关键字LIKE用在WHERE子句中时&#xff0c;可以比较两个字符串的部分匹配。当对字符串内容有些印象&#xff0c;但并不知…

AND运算符

6.2 组合查询条件 在前一章提到的WHERE子句进行查询时&#xff0c;WHERE子句后面的搜索条件只是单一的。实际上&#xff0c;可以通过布尔运算符AND和OR&#xff0c;将多个单独的搜索条件结合在一个WHERE子句中&#xff0c;形成一个复合的搜索条件。当对复合搜索条件求值时&a…

OR运算符

6.2.2 OR运算符 OR运算符表示“或”的关系。当可能有多个条件为True&#xff0c;但只要有一个为True就满足搜索要求时&#xff0c;可以使用OR运算符来组合搜索条件。OR在结合两个布尔表达式时&#xff0c;只要其中一个条件为True时&#xff0c;便传回True。OR运算符的真值表…

Java基础---网络编程

第一讲 概述 1、网络模型&#xff1a;OSI参考模型和TCP/IP参考模型 图示&#xff1a; 一般来说开发处于传输层和网际层&#xff0c;应用层为&#xff1a;FTP和HTTP协议等&#xff0c;传输层为&#xff1a;UDP和TCP等&#xff0c;网际层为&#xff1a;IP。 通常用户操作的是…

AND、OR运算符的组合使用

6.2.3 AND、OR运算符的组合使用 在WHERE子句中&#xff0c;通过AND、OR运算符可以同时连接多个条件&#xff0c;当然AND、OR运算符也可以同时使用。但是当AND、OR运算符同时存在时&#xff0c;其优先级如何确定呢&#xff1f;与大多数语言一样&#xff0c;SQL语言认为AND运算…

IN运算符的使用

6.3 IN运算符 在查询中&#xff0c;有时只要满足多个条件中的一个条件即可&#xff0c;如查询地址在北京、上海或者重庆的学生信息&#xff0c;这时候可以使用IN运算符。 6.3.1 IN运算符的使用 IN运算符允许根据一行记录中&#xff0c;是否有一列包括在一系列值之中&#…

NOT运算符与运算符

6.4.2 NOT运算符与<>运算符 对于简单的条件查询&#xff0c;NOT运算符与<>运算符的功能几乎没有什么区别&#xff0c;那么NOT运算符的优势体现在哪里呢&#xff1f;答案是它可以与其他运算符组合使用&#xff0c;这一点是<>运算符所不能实现的。在6.4.1节已…

“%”通配符

6.5.2 “%”通配符 在SQL语言中最常用的通配符可能就是“%”了&#xff0c;它表示任意字符的匹配&#xff0c;且不计字符的多少。下面通过几个典型实例来说明“%”通配符的使用。 1&#xff0e;开头&#xff0f;结尾匹配 从COURSE表中查询所有以“计算机”开头的所有课程的…

Wireshark基本介绍和学习TCP三次握手

Wireshark基本介绍和学习TCP三次握手 原文&#xff1a;http://www.cnblogs.com/TankXiao/archive/2012/10/10/2711777.html wireshark介绍 wireshark的官方下载网站&#xff1a; http://www.wireshark.org/ wireshark是非常流行的网络封包分析软件&#xff0c;功能十分强大。可…

“_”通配符

6.5.3 “_”通配符 “_”通配符的功能与“%”通配符基本相同&#xff0c;只是它只表示任意一个字符的匹配。当然&#xff0c;要表示两个字符的匹配&#xff0c;就需要使用两个“_”通配符&#xff0c;即写成“__”。 只有在用户确定所要查询的字符串的个数&#xff0c;只是不…

“[]”通配符

6.5.4 “[]”通配符 “[]”通配符用于指定一系列的字符&#xff0c;只要满足这些字符其中之一&#xff0c;且位置出现在“[]”通配符的位置的字符串就满足查询条件。 当然&#xff0c;各种通配符也可以组合使用。组合使用各种通配符时&#xff0c;一定要弄清其表示的匹配条…

QT分页控件,开源,供大家使用

下载地址&#xff1a;http://files.cnblogs.com/dragonsuc/qt5.rar 转载于:https://www.cnblogs.com/dragonsuc/p/4242342.html

STL学习小结

STL就是Standard Template Library&#xff0c;标准模板库。这可能是一个历史上最令人兴奋的工具的最无聊的术语。从根本上说&#xff0c;STL是一些“容器”的集合&#xff0c;这些“容器”有list, vector,set,map等&#xff0c;STL也是算法和其它一些组件的集合。这里的“容器…

内连接(INNER JOIN)

9.3.3 内连接&#xff08;INNER JOIN&#xff09; 内连接也称为等同连接&#xff0c;返回的结果集是两个表中所有相匹配的数据&#xff0c;而舍弃不匹配的数据。也就是说&#xff0c;在这种查询中&#xff0c;DBMS只返回来自源表中的相关的行&#xff0c;即查询的结果表包含的…

外连接(OUTER JOIN)

9.3.4 外连接&#xff08;OUTER JOIN&#xff09; 不管是内连接还是带WHERE子句的多表查询&#xff0c;都组合自多个表&#xff0c;并生成结果表。换句话说&#xff0c;如果任何一个源表中的行在另一个源表中没有匹配&#xff0c;DBMS将不把该行放在最后的结果表中。 而外连…

Android应用切换皮肤功能实现

原文地址&#xff1a;http://www.eoeandroid.com/thread-318159-1-1.html 现在大多数android应用都支持切换皮肤的功能。比如千千静听&#xff0c;墨迹天气等等。本文介绍两种切换皮肤的方法。1.第一种是通过安装皮肤apk的方式。当安装了皮肤apk包之后&#xff0c;主程序只需要…