基于纤程(Fiber)实现C++异步编程库(一):原理及示例
纤程(Fiber)和协程(coroutine)是差不多的概念,也叫做用户级线程或者轻线程之类的。Windows系统提供了一组API用户创建和使用纤程,本文中的库就是基于这组API实现的,所以无法跨平台使用,非Windows程序员可以闪人了,当然如果有兴趣可以继续看下去,找个第三方的协程库封装一下,也能实现相同的效果。关于纤程更详细的信息可以查阅MSDN。
纤程的概念中有两个关键点:
- 纤程拥有独立的栈空间和寄存器环境;
- 纤程在用户态实现调调度,也就是说完全由程序员控制;
下图演示了几个纤程相互切换的过程,注意每个纤程都有独立的栈,并且通过SwitchToFiber函数切换到其他纤程:
作为对比,我们可以看一下函数调用过程中的堆栈变化情况,下面是示意图,表示了func1 -> func2 -> func3 这种常见的函数嵌套调用关系:
每一次函数调用都会创建一个新的栈帧(stack frame),合起来就构成整个调用栈,函数返回时其栈帧也随之释放。对于函数调用,我们可以确定的一点是(在不抛出异常的情况下)被调用函数执行完毕后一定会在调用点返回并继续执行下一条语句。但纤程之间的调用(切换)却不同,一个纤程可以在任意位置切换到其他纤程,并且可能永远都不会再切换回来,也可能从其他任意纤程(不必是刚刚切换到的)切换回来,前面的示意图描述的只是一种非常简单的情况,实际的情况可能非常复杂,复杂到导出都是跳来跳去的箭头理也理不清。在纤程间切换,有点像用加强版的goto,用的时候固然很爽,但后续的维护却是个麻烦。
所以就像用while/for/switch-case代替goto一样,我们也需要封装一组新的API来代替对操作系统API的直接调用。一方面,在封装过程中我们可以对纤程的行为(实际是程序员的行为)施加一些安全约束,使得更容易写出安全的代码或者更不容易写出不安全的代码;另一方面,从goto到while/switch等过程控制语句实际上是一种抽象层次的提升,对大部分常见需求后者用起来更方便,更不容易出错,写出的代码也更简洁易懂,类似的,从系统API到新的封装API或者封装类也是抽象层次的提高,可以更方便的应用在各种业务场景;最后,直接使用系统API需要写很多维护纤程的辅助代码,这类代码通常重复而又分散到业务代码的各个角落,进一步降低了程序的可读性和提高了维护难度,封装也是为了解决这个问题。
好了,废话说完了,我们先上一段代码尝尝鲜:
const int RUN_TIMES = ; int number = ;
bool shutdown = false; Fiber fib([&number, &shutdown]
{
while (!shutdown)
{
number++;
Fiber::yield(); // A:控制权移交到主纤程
}
}); for (int i = ; i < RUN_TIMES; i++)
{
fib.resume(); // B: 切换到子纤程执行
} printf("number = %d\r\n", number);
这里先创建了一个纤程实现number变量累加的功能,然后在for循环中执行(姑且用这个词)最终得到正确的结果。AB两处代码分别实现了纤程的切换,实际上是封装了对SwitchToFiber的调用,注意两个函数调用细节上的不同:resume表示切换到对象包装的纤程,是普通成员函数,yield表示控制权移交给调用者纤程,是静态成员函数,大家可以思考下为什么有静态和非静态成员函数的差别。
下面是用纤程实现生产者-消费者模型的代码:
int product_count = ;
bool is_end_time = false; const int RUN_TIMES = ; // 生产者纤程
Fiber fib_producer([&is_end_time, &product_count]
{
srand((unsigned)time(NULL)); while (!is_end_time)
{
int new_product_count = (int)((double)rand() / RAND_MAX * ) + ;
product_count += new_product_count; printf("[producer] create new products: %d\r\n", new_product_count); Fiber::yield();
} printf("[producer] off duty.\r\n");
}); // 消费者纤程的执行函数
auto consumer_proc = [&is_end_time, &product_count](const int seq_number)
{
int total_count = ; while (!is_end_time)
{
if (product_count > )
{
product_count--;
total_count++;
printf("[consumer %d] got 1 product, total got %d, remain %d\r\n", seq_number, total_count, product_count);
} Fiber::yield();
} printf("[consumer %d] off duty.\r\n", seq_number);
}; const int CONSUMER_COUNT = ;
int consumer_seq_number = ; // 创建消费者纤程数组
std::vector<Fiber> consumer_array(CONSUMER_COUNT);
std::for_each(consumer_array.begin(), consumer_array.end(), [&](Fiber& item){ item = Fiber([&]{ consumer_proc(consumer_seq_number); }); consumer_seq_number++; }); consumer_seq_number = ; for (int i = ; i < RUN_TIMES; i++)
{
fib_producer.resume(); while (product_count > )
{
consumer_array[consumer_seq_number].resume();
consumer_seq_number = (consumer_seq_number + ) % CONSUMER_COUNT;
}
} is_end_time = true; // 等待纤程结束
Fiber::await_all(consumer_array);
Fiber::await(fib_producer);
程序末尾出现了await和await_all两个新的方法可以先不用管,不影响主要逻辑。由于所有纤程都是在同一个线程中运行的所以无需加锁,这也是使用纤程的一个重要好处,也是我们这个封装库的主要目的之一。
限于篇幅,这次就只写这么多了,更多的内容将放到后面的帖子中,总计还要写四、五篇的样子。但代码实际上已经写完了,急性子的园友可以直接到这个地址看代码:
https://code.csdn.net/xrunning/fiber
建了一个QQ群:微观架构设计165241092,主要讨论C++代码级设计,感兴趣的园友加进来一起讨论学习。
基于纤程(Fiber)实现C++异步编程库(一):原理及示例的更多相关文章
- 阿里开源 iOS 协程开发框架 coobjc!--异步编程的问题与解决方案
阿里妹导读:刚刚,阿里巴巴正式对外开源了基于 Apache 2.0 协议的协程开发框架 coobjc,开发者们可以在 Github 上自主下载.coobjc是为iOS平台打造的开源协程开发框架,支持O ...
- 第12章 纤程(Fiber)
12.1 纤程对象的介绍 (1)纤程与线程的比较 比较 线程(Thread) 纤程(Fiber) 实现方式 是个内核对象 在用户模式中实现的一种轻量级的线程,是比线程更小的调度单位. 调度方式 由Mi ...
- 利用python yielding创建协程将异步编程同步化
转自:http://www.jackyshen.com/2015/05/21/async-operations-in-form-of-sync-programming-with-python-yiel ...
- 深入理解 Python 异步编程(上)
http://python.jobbole.com/88291/ 前言 很多朋友对异步编程都处于"听说很强大"的认知状态.鲜有在生产项目中使用它.而使用它的同学,则大多数都停留在知 ...
- 最新Python异步编程详解
我们都知道对于I/O相关的程序来说,异步编程可以大幅度的提高系统的吞吐量,因为在某个I/O操作的读写过程中,系统可以先去处理其它的操作(通常是其它的I/O操作),那么Python中是如何实现异步编程的 ...
- 继续了解Java的纤程库 – Quasar
前一篇文章Java中的纤程库 – Quasar中我做了简单的介绍,现在进一步介绍这个纤程库. Quasar还没有得到广泛的应用,搜寻整个github也就pinterest/quasar-thrift这 ...
- 【憩园】C#并发编程之异步编程(一)
写在前面 C#5.0中,对异步编程进行了一次革命性的重构,引入了async和await这两个关键字,使得开发人员在不需要深刻了解异步编程的底层原理,就可以写出十分优美而又代码量极少的代码.如果使用得当 ...
- 使用任务Task 简化异步编程
使用任务简化异步编程 Igor Ostrovsky 下载代码示例 异步编程是实现与程序其余部分并发运行的较大开销操作的一组技术. 常出现异步编程的一个领域是有图形化 UI 的程序环境:当开销较大的操作 ...
- 协程,纤程(Fiber),或者绿色线程(GreenThread)
纤程(Fiber),或者绿色线程(GreenThread) 面试官:你知道协程吗? 你:订机票的那个吗,我常用. 面试官:行,你先回去吧,到时候电话联系 ........ 很尴尬,但是事实是,很大一部 ...
随机推荐
- C# 高效率创建字符串类(StringBuilder)
1.为什么需要StringBuilder类? 因为String类型代表不可变字符串,所以无法对当前String类型实例进行处理.所以FCL提供了System.Text.StringBuilder类型, ...
- 二、LINQ之查询表达式基础
1.查询是什么? 查询是一组指令,描述要从给定数据源(或源)检索的数据以及返回的数据应具有的形状和组织.查询表达式和它所产生的结果不同.
- 业余实现一个统计A股数据工具
自己瞎捣鼓了几天 python,数据来源新浪财经,每天收盘启动爬虫抓取一遍,web 端呈现日线与周线数据:实时图表显示上证指数与个股指数等.技术点:scrapy apscheduler sqlalch ...
- 03-01:springboot 整合jsp
1.修改pom文件,添加坐标 <!-- jstl --> <dependency> <groupId>javax.servlet ...
- 全网最详细的Windows系统里PLSQL Developer 64bit安装之后的一些配置(图文详解)
不多说,直接上干货! 注意的是: 本地若没有安装Oracle服务端,Oracle server服务端64位,是远程连接,因此本地配置PLSQL Developer64位. PLSQL Develope ...
- 全网最详细的Hadoop HA集群启动后,两个namenode都是standby的解决办法(图文详解)
不多说,直接上干货! 解决办法 因为,如下,我的Hadoop HA集群. 1.首先在hdfs-site.xml中添加下面的参数,该参数的值默认为false: <property> < ...
- sublime自动对齐Alignment插件快捷键
[ { "keys": ["ctrl+alt+f"], "command": "alignment" } ]
- 高可用Hadoop平台-实战尾声篇
1.概述 今天这篇博客就是<高可用Hadoop平台>的尾声篇了,从搭建安装到入门运行 Hadoop 版的 HelloWorld(WordCount 可以称的上是 Hadoop 版的 Hel ...
- css outline实践研究
outline具有和border很相似的属性,但多少又有些区别,就是因为这些区别才让它闪闪发光,先目睹一下. <style> div{ width:100px; height:100px; ...
- spring boot 与 thymeleaf (1): 国际化
在thymeleaf 里面有个消息表达式: #{...} , 可以借此来实现国际化. 在我使用这个功能的时候, 碰到了一个问题, 按照 JavaEE开发的颠覆者 Spring Boot实战 上面编码 ...