在线编程在线课堂在线测评Anycodes在线编程

编程论坛

 找回密码
 立即注册

QQ登录

只需一步,快速开始

How to use bs4??
本帖最后由 carry0987 于
Double Queue 问题描述 : The new founded Balkan Investment Group Bank (
John 问题描述 : Little John is playing very funny game
linux-command Linux命令大全搜索工具,内容包含Linux命令
Coati 是一款跨平台的代码查看工具,适用于 C/C++ 和 Java。商业软件。特性:1. 索引
系统可承载海量并发,消息收发确认机制 保障消息必达 系统采用动态智
全平台视频监控,支持安卓苹果以及pcweb,支持海康大华等主流dvr,全部源码以及文档 单聊、群聊、商
如何访问类的私有属性? 下面以 TPathData 为例,
问题:从 XE4 以来,Firemonkey 曲线绘图在移动平台不平滑的问题一直令人诟病,提交到官方的 QC 也是族繁不及备载,官方似乎有意的
操作数据库(RODBC)   odbcConnect(dsn, uid="", p
数据模式:mode函数显示任何对象的模式。常见的单个的
系统可承载海量并发,消息收发确认机制 保障消息必达 系统采用动态智
RabbitMQ与PHP(一) 项
Iease团队扩编预备中,盼望能有Ruby或者java工程师加盟。全职兼职都可以。有爱好的伴侣请与我接洽。 邮件:i
ruby 怎么设置装备摆设GTK2,求教指导下!
#include #include #include #include using namespace std; int main() {
标题如图所示: 有n盏灯,编号1~n。一开端灯都是关着的
成熟的消息收发确认机制,支持万人大群 支持开发自定义的消息sdk接口,扩展性超强 支持单/
成熟的消息收发确认机制,支持万人大群 支持开发自定义的消息sdk接口,扩展性超强 支持单/
1. 注意列表和集合的区别 set 列表表现形式: list_1
Ajax   Ajax即“Asynchronous Javascript And
大师好,我比来在做布谷鸟优
分辨提取A和B图像Harris角点,接下来须要对

[C++分享] 一种协程的 C/C++ 实现

[复制链接]
发表于 2016-4-27 19:03:01 | 显示全部楼层 |阅读模式
一种协程的 C/C++ 实现

介绍

在前几天接触到了协程的概念,觉得很有趣。因为我可以使用一个线程来实现一个类似多线程的程序,如果使用协程来替代线程,就可以省去很多原子操作和内存栅栏的麻烦,大大减少与线程同步相关的系统调用。因为我只有一个线程,而且协程之间的切换是可以由函数自己决定的。
我有见过几种协程的实现,因为没有 C/C++ 的原生支持,所以多数的库使用了汇编代码,还有些库利用了 C 语言的 setjmp 和 longjmp 但是要求函数里面使用 static local 的变量来保存协程内部的数据。我讨厌写汇编和使用 static local 变量,所以想出了一种稍微优雅一点又有点奇技淫巧的实现方法。 这篇文章将向你展示这种方法基本原理和实现。
基本原理

用 C/C++ 实现的最大困难就是创建,保存和恢复程序的上下文。因为这涉及到了程序栈的管理,以及 CPU 寄存器的访问,但是这两项内容在 C/C++ 标准里面都没有严格的定义,所以我们是不可能有一个完全跨平台的 C/C++ 实现的。但是利用操作系统提供的 API,我们仍然可以避免使用汇编代码,接下来会向你展示使用 POSIX 的 pthread 实现的一种简单的协程框架。什么!??Pthread?那你的程序岂不是多线程了?那还叫协程吗!没错,确实是多线程的,不过仅仅是在协程被创建之前的短暂瞬间。
要创建子程序的上下文,我们可以调用 pthread_create 函数来创建一个真正的线程,这样操作系统就会帮我们创建上下文(这里包括初始化 CPU 寄存器和程序栈)。然后在线程启动时,使用 C 语言的 setjmp 把这些寄存器备份到外部的 buffer 里面。创建完后,这个线程便失去了它的存在价值,所以可以果断干掉它了。不过还需要注意一点,就是在创建线程之前,需要调用 pthread_attr_setstack 函数来显式地声明使用的程序栈,这样线程退出的时候,系统就不会自动销毁这个程序栈。至于上下文的恢复,显然就是使用 longjmp 函数了。
创建上下文

下面是 RoutineInfo 的定义。为了简单起见,所有错误处理代码都被省略了,原版本的代码在 coroutine.cpp 文件中,省略版的代码在 coroutine_demonstration.cpp 文件中。
  1. typedef void * (*RoutineHandler)(void*);
  2. struct RoutineInfo{
  3.     void * param;
  4.     RoutineHandler handler;
  5.     void * ret;
  6.     bool stopped;
  7.     jmp_buf buf;
  8.    
  9.     void *stackbase;
  10.     size_t stacksize;
  11.    
  12.     pthread_attr_t attr;
  13.    
  14.     // size: the stack size
  15.     RoutineInfo(size_t size){
  16.         param = NULL;
  17.         handler = NULL;
  18.         ret = NULL;
  19.         stopped = false;
  20.         stackbase = malloc(size);
  21.         stacksize = size;
  22.         pthread_attr_init(&attr);
  23.         if(stacksize)
  24.             pthread_attr_setstack(&attr,stackbase,stacksize);
  25.     }
  26.    
  27.     ~RoutineInfo(){
  28.         pthread_attr_destroy(&attr);
  29.         free(stackbase);
  30.     }
  31. };
复制代码
然后,我们需要一下全局的列表来保存这些 RoutineInfo 对象。
  1. std::list<RoutineInfo*> InitRoutines(){
  2.     std::list<RoutineInfo*> list;
  3.     RoutineInfo *main = new RoutineInfo(0);
  4.     list.push_back(main);
  5.     return list;
  6. }
  7. std::list<RoutineInfo*> routines = InitRoutines();
复制代码
接下来是协程的创建,注意当协程的时候,程序栈有可能已经被损坏了,所以需要一个 stackBack 作为程序栈的备份,用来做后面的恢复。
  1. void *stackBackup = NULL;
  2. void *CoroutineStart(void *pRoutineInfo);
  3. int CreateCoroutine(RoutineHandler handler,void* param ){
  4.     RoutineInfo* info = new RoutineInfo(PTHREAD_STACK_MIN+ 0x4000);
  5.     info->param = param;
  6.     info->handler = handler;
  7.     pthread_t thread;
  8.     int ret = pthread_create( &thread, &(info->attr), CoroutineStart, info);
  9.     void* status;
  10.     pthread_join(thread,&status);
  11.     memcpy(info->stackbase,stackBackup,info->stacksize);    // restore the stack
  12.     routines.push_back(info);   // add the routine to the end of the list
  13.    
  14.     return 0;
  15. }
复制代码
然后是 CoroutinneStart 函数。当线程进入这个函数的时候,使用 setjmp 保存上下文,然后备份它自己的程序栈,然后直接退出线程。
  1. void Switch();
  2. void *CoroutineStart(void *pRoutineInfo){
  3.     RoutineInfo& info = *(RoutineInfo*)pRoutineInfo;
  4.     if( !setjmp(info.buf)){
  5.         // back up the stack, and then exit
  6.         stackBackup = realloc(stackBackup,info.stacksize);
  7.         memcpy(stackBackup,info.stackbase, info.stacksize);
  8.         pthread_exit(NULL);
  9.         return (void*)0;
  10.     }
  11.     info.ret = info.handler(info.param);
  12.    
  13.     info.stopped = true;
  14.     Switch(); // never return
  15.    
  16.     return (void*)0; // suppress compiler warning
  17. }
复制代码
上下文切换

一个协程主动调用 Switch() 函数,才切换到另一个协程。
  1. std::list<RoutineInfo*> stoppedRoutines = std::list<RoutineInfo*>();
  2. void Switch(){
  3.     RoutineInfo* current = routines.front();
  4.     routines.pop_front();
  5.    
  6.     if(current->stopped){
  7.         // The stack is stored in the RoutineInfo object,
  8.         // delete the object later, now know
  9.         stoppedRoutines.push_back(current);
  10.         longjmp( (*routines.begin())->buf ,1);
  11.     }
  12.    
  13.     routines.push_back(current);        // adjust the routines to the end of list
  14.    
  15.     if( !setjmp(current->buf) ){
  16.         longjmp( (*routines.begin())->buf ,1);
  17.     }
  18.    
  19.     if(stoppedRoutines.size()){
  20.         delete stoppedRoutines.front();
  21.         stoppedRoutines.pop_front();
  22.     }
  23. }
复制代码
演示

用户的代码很简单,就像使用一个线程库一样,一个协程主动调用 Switch() 函数主动让出 CPU 时间给另一个协程。
  1. #include <iostream>
  2. using namespace std;
  3. #include <sys/wait.h>
  4. void* foo(void*){
  5.     for(int i=0; i<2; ++i){
  6.         cout<<"foo: "<<i<<endl;
  7.         sleep(1);
  8.         Switch();
  9.     }
  10. }
  11. int main(){
  12.     CreateCoroutine(foo,NULL);
  13.     for(int i=0; i<6; ++i){
  14.         cout<<"main: "<<i<<endl;
  15.         sleep(1);
  16.         Switch();
  17.     }
  18. }
复制代码
记得在链接的时候加上 -lpthread 链接选项。程序的执行结果如下所示:
  1. [roxma@VM_6_207_centos coroutine]$ g++ coroutime_demonstration.cpp -lpthread -o a.out
  2. [roxma@VM_6_207_centos coroutine]$ ls
  3. a.out  coroutime.cpp  coroutime_demonstration.cpp  README.md
  4. [roxma@VM_6_207_centos coroutine]$ ./a.out
  5. main: 0
  6. foo: 0
  7. main: 1
  8. foo: 1
  9. main: 2
  10. main: 3
  11. main: 4
  12. main: 5
复制代码
原文及代码下载

https://github.com/roxma/cpp_learn/tree/master/cpp/linux_programming/coroutine



上一篇:TNF-mutithread 编译过程记录
下一篇:vs2013 c++ Win32的DLL项目引用时犯错 [不是有用 的 Win32 利用 法式 ]
在线编程(http://www.anycodes.cn)&编程论坛(http://www.52exe.cn)感谢您的支持!
回复

使用道具 举报

发表于 2016-5-7 01:03:43 | 显示全部楼层
顶顶顶
在线编程(http://www.anycodes.cn)&编程论坛(http://www.52exe.cn)感谢您的支持!
回复 支持 反对

使用道具 举报

发表于 2016-5-7 13:32:53 | 显示全部楼层
有多少新手
在线编程(http://www.anycodes.cn)&编程论坛(http://www.52exe.cn)感谢您的支持!
回复 支持 反对

使用道具 举报

发表于 2016-5-8 22:54:23 | 显示全部楼层
啦啦啦啦
在线编程(http://www.anycodes.cn)&编程论坛(http://www.52exe.cn)感谢您的支持!
回复 支持 反对

使用道具 举报

发表于 2016-5-17 14:13:44 | 显示全部楼层
写的不错
在线编程(http://www.anycodes.cn)&编程论坛(http://www.52exe.cn)感谢您的支持!
回复 支持 反对

使用道具 举报

发表于 2016-8-1 02:00:53 | 显示全部楼层
一言难尽
在线编程(http://www.anycodes.cn)&编程论坛(http://www.52exe.cn)感谢您的支持!
回复 支持 反对

使用道具 举报

发表于 2016-8-17 03:54:48 | 显示全部楼层
写的真心真心真心不错
在线编程(http://www.anycodes.cn)&编程论坛(http://www.52exe.cn)感谢您的支持!
回复 支持 反对

使用道具 举报

发表于 2016-11-12 06:12:44 | 显示全部楼层
支持Anycodes
在线编程(http://www.anycodes.cn)&编程论坛(http://www.52exe.cn)感谢您的支持!
回复 支持 反对

使用道具 举报

发表于 2016-11-18 19:49:07 | 显示全部楼层
约不约
在线编程(http://www.anycodes.cn)&编程论坛(http://www.52exe.cn)感谢您的支持!
回复 支持 反对

使用道具 举报

发表于 2017-2-17 09:03:07 | 显示全部楼层
在线编程(http://www.anycodes.cn)&编程论坛(http://www.52exe.cn)感谢您的支持!
回复 支持 反对

使用道具 举报

发布主题 上个主题 下个主题 快速回复 返回列表 官方QQ群
在线客服
客 服 中 心
群 机 器 人
网站二维码
收 起 客 服

QQ|Archiver|手机版|小黑屋|Anycodes ( ICP14002806Anycodes在线编程

GMT+8, 2018-11-22 01:10 , Processed in 2.749533 second(s), 96 queries .

Powered by Anycodes

© 2001-2013 吉林市群龙科技有限公司 Inc.

快速回复 返回顶部 返回列表