用C++模拟异步编程
acmilan2016/05/28软件综合 IP:四川
C# 5.0和JavaScript ES6是适合于异步编程的语言,因为有async/await,但是实际上,用C++也是可以模拟出异步编程的。
注意这个程序只是实现了异步编程大致的逻辑,还有一些细节没照顾到,也可能有错误,仅供学习研究之用。

CaptureAsyncCpp.png
<code class="lang-cpp">// coroutine.cpp : 定义控制台应用程序的入口点。
//
                                 
#include "stdafx.h"
                                 
#include <stdio.h>
#include <windows.h>
#include <queue>
#include <tuple>
                                 
using namespace std;
                                 
struct CoContext;
typedef void (*CoRoutine)(CoContext*);
                                 
queue<pair<int, cocontext*>> resumequeue;
                                 
struct CoContext {
    CoContext(
        CoRoutine pfunc, INT_PTR resume1 = 0, INT_PTR resume2 = 0, INT_PTR resume3 = 0,
        CoContext* resumecontext = NULL, int resumetick = 1, bool finished = false)
        : pfunc(pfunc), resume1(resume1), resume2(resume2), resume3(resume3),
        resumecontext(resumecontext), resumetick(resumetick), finished(finished)
    {
        // 构造函数
    }
    CoRoutine pfunc;
    INT_PTR resume1;
    INT_PTR resume2;
    INT_PTR resume3;
    CoContext *resumecontext;
    int resumetick;
    bool finished;
};
                                 
void pushcontext(int tick, CoContext *ctx)
{
    resumequeue.push(pair<int, cocontext*>(tick, ctx));
}
                                 
void coroutine1(CoContext *ctx);
                                 
int main()
{
    CoContext *ctx = new CoContext(coroutine1);
    pushcontext(1, ctx);
                                 
    while (1)
    {
        if (!resumequeue.empty() && --resumequeue.front().first <= 0)         {             cocontext *ctx="resumequeue.front().second;"             if (ctx->finished)
            {
                if (ctx->resumecontext)
                    pushcontext(ctx->resumetick, ctx->resumecontext);
                else
                    delete ctx; // 此处存在问题,见06-01更新说明
            }
            else
            {
                ctx->pfunc(ctx);
            }
            resumequeue.pop();
        }
        Sleep(1);
    }
    return 0;
}
                                 
////////////////////////////////////////////////////////////
// 两个异步函数
// coroutine1调用coroutine2
// coroutine2返回coroutine1但仍在工作(这里以空等来模拟)
// coroutine1设置coroutine2的恢复点为自己,并等待(返回消息循环)
// coroutine2工作完成,设置finished标志
// 消息循环检测到了finished标志,恢复coroutine1的状态
// coroutine1恢复以后删除coroutine2的状态
// coroutine1完成并返回消息循环
// 消息循环检测到了finished标志,由于恢复点为空,将删除它的状态
                                 
void coroutine2(CoContext *ctx)
{
    if (ctx->resume1 == 1) goto resume1; // 跳到恢复点
                                 
    // 空等待1000ms
    printf("just suspend 200 ticks\n");
    ctx->resume1 = 1;
    return pushcontext(200, ctx);
resume1:
    printf("after suspend 200 ticks\n");
                                 
    // 处理完成并返回
    printf("set finished\n");
    ctx->finished = true;
    return pushcontext(1, ctx);
}
                                 
void coroutine1(CoContext *ctx)
{
    if (ctx->resume1 == 1) goto resume1; // 跳到恢复点
                                 
    // 调用异步函数
    printf("before calling\n");
    CoContext *ctx2 = new CoContext(coroutine2);
    coroutine2(ctx2);
    printf("after calling\n");
                                 
    // 等待await
    printf("before awaiting\n");
    ctx2->resumecontext = ctx;
    ctx2->resumetick = 1;
    ctx->resume1 = 1;
    ctx->resume2 = (INT_PTR)ctx2;
    return;
resume1:
    delete (CoContext*)ctx->resume2;
    printf("after awaiting\n");
                                     
    // 处理完成并返回
    printf("set finished\n"); 
    ctx->finished = true;
    return pushcontext(1, ctx);
}</=></int,></pair<int,></tuple></queue></windows.h></stdio.h></code>

【06-01更新】
对于上面的程序,如果程序调用了异步函数并没有立即await同一个函数,而是await别的去了,那么将会导致ctx被提前delete。
更新内容:增加abandoned标志,设置此标志表示放弃await,消息循环将自动回收ctx,否则需手动回收。
<code class="lang-cpp">// coroutine.cpp : 定义控制台应用程序的入口点。
//
                                  
#include "stdafx.h"
                                  
#include <stdio.h>
#include <windows.h>
#include <queue>
                                  
using namespace std;
                                  
struct CoContext;
typedef void (*CoRoutine)(CoContext*);
                                  
queue<pair<int, cocontext*>> resumequeue;
                                  
struct CoContext {
    CoContext(
        CoRoutine pfunc, INT_PTR resume1 = 0, INT_PTR resume2 = 0, INT_PTR resume3 = 0,
        CoContext* resumecontext = NULL, int resumetick = 1, bool finished = false, bool abandoned = false)
        : pfunc(pfunc), resume1(resume1), resume2(resume2), resume3(resume3),
        resumecontext(resumecontext), resumetick(resumetick), finished(finished), abandoned(abandoned)
    {
        // 构造函数
    }
    CoRoutine pfunc;
    INT_PTR resume1;
    INT_PTR resume2;
    INT_PTR resume3;
    CoContext *resumecontext;
    int resumetick;
    bool finished;
    bool abandoned;
};
                                  
void pushcontext(int tick, CoContext *ctx)
{
    resumequeue.push(pair<int, cocontext*>(tick, ctx));
}
                                  
void coroutine1(CoContext *ctx);
                                  
int main()
{
    CoContext *ctx = new CoContext(coroutine1);
    ctx->abandoned = true;
    pushcontext(1, ctx);
                                  
    while (1)
    {
        if (!resumequeue.empty() && --resumequeue.front().first <= 0)         {             cocontext *ctx="resumequeue.front().second;"             if (ctx->finished)
            {
                if (ctx->resumecontext)
                    pushcontext(ctx->resumetick, ctx->resumecontext);
                else if (ctx->abandoned)
                    delete ctx;
                else
                    pushcontext(1, ctx);
            }
            else
            {
                ctx->pfunc(ctx);
            }
            resumequeue.pop();
        }
        Sleep(1);
    }
    return 0;
}
                                  
////////////////////////////////////////////////////////////
// 两个异步函数
// coroutine1调用coroutine2
// coroutine2返回coroutine1但仍在工作(这里以空等来模拟)
// coroutine1设置coroutine2的恢复点为自己,并等待(返回消息循环)
// coroutine2工作完成,设置finished标志
// 消息循环检测到了finished标志,恢复coroutine1的状态
// coroutine1恢复以后删除coroutine2的状态
// coroutine1完成并返回消息循环
// 消息循环检测到了finished标志,由于abandoned=true,将删除它的状态
                                  
void coroutine2(CoContext *ctx)
{
    if (ctx->resume1 == 1) goto resume1; // 跳到恢复点
                                  
    // 空等待1000ms
    printf("just suspend 200 ticks\n");
    ctx->resume1 = 1;
    return pushcontext(200, ctx);
resume1:
    printf("after suspend 200 ticks\n");
                                  
    // 处理完成并返回
    printf("set finished\n");
    ctx->finished = true;
    return pushcontext(1, ctx);
}
                                  
void coroutine1(CoContext *ctx)
{
    if (ctx->resume1 == 1) goto resume1; // 跳到恢复点
                                  
    // 调用异步函数
    printf("before calling\n");
    CoContext *ctx2 = new CoContext(coroutine2);
    coroutine2(ctx2);
    printf("after calling\n");
                                  
    // 等待await
    printf("before awaiting\n");
    ctx2->resumecontext = ctx;
    ctx2->resumetick = 1;
    ctx->resume1 = 1;
    ctx->resume2 = (INT_PTR)ctx2;
    return;
resume1:
    delete (CoContext*)ctx->resume2;
    printf("after awaiting\n");
                                      
    // 处理完成并返回
    printf("set finished\n");
    ctx->finished = true;
    return pushcontext(1, ctx);
}</=></int,></pair<int,></queue></windows.h></stdio.h></code>

[修改于 7年11个月前 - 2016/06/01 17:43:42]

来自:计算机科学 / 软件综合
12
已屏蔽 原因:{{ notice.reason }}已屏蔽
{{notice.noticeContent}}
~~空空如也
.........
7年11个月前 IP:浙江
820232
手上有个FreeRTOS的模拟器是用PThread实现的
引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论
金星凌日
7年11个月前 IP:陕西
820242
感觉这样的异步比JavaScript中的异步更不自然。
如果用Lua,可以让异步代码形式上看起来是同步的,但实际上是异步执行。这样的代码人更容易理解。
引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论
acmilan作者
7年11个月前 IP:四川
820245
引用 金星凌日:
感觉这样的异步比JavaScript中的异步更不自然。
如果用Lua,可以让异步代码形式上看起来是同步的,但实际上是异步执行。这样的代码人更容易理解。
实际上,javascript中的基于闭包的异步编程重新回到了面条式编程,lua或者c# 5.0或es6的async/await(coroutine)机制对异步编程最大的贡献是实现了结构化异步编程。
引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论
金星凌日
7年11个月前 IP:陕西
820246
引用 acmilan:
实际上,javascript中的基于闭包的异步编程重新回到了面条式编程,lua或者c# 5.0或es6的async/await(coroutine)机制对异步编程最大的贡献是实现了结构化异步编程。
我记得类似的东西在Scheme里也出现过。
引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论
amo
7年11个月前 IP:广东
820438
不友好……
建议搜索“protothread”了解一下。这是uip带的。uip和lwip的作者出品
引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论
acmilan作者
7年11个月前 修改于 7年11个月前 IP:四川
820468
引用 amo:
不友好……
建议搜索“protothread”了解一下。这是uip带的。uip和lwip的作者出品
这个只是一个原型程序(演示程序),并不是实用程序。。。(实用需要语法糖或宏的支持)

protothread主要是为了并行编程而设计的,并没有针对异步串行编程设计的队列和上下文管理。。。
对异步串行编程友好的语言,需要具有async/await机制,上面已经说过了,比如c# 5.0、javascript es6、lua等。。。
引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论
amo
7年11个月前 IP:广东
820506
引用 acmilan:
这个只是一个原型程序(演示程序),并不是实用程序。。。(实用需要语法糖或宏的支持)

protothread主要是为了并行编程而设计的,并没有针对异步串行编程设计的队列和上下文管理。。。
对异步串行编程友好的语言,需要具有async/...
你说的“并行”和“异步”,实际上是同一个玩意……
编译器的实现,也是转为状态机。但是在C/C++里protothread的兼容性更好,天生就是跨平台的,同一份代码在单片机、windows、linux……上都能跑,不管用什么C/C++编译器、带不带OS。只需要简单包含一个短小精悍的头文件
引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论
acmilan作者
7年11个月前 IP:四川
820562
引用 amo:
你说的“并行”和“异步”,实际上是同一个玩意……
编译器的实现,也是转为状态机。但是在C/C++里protothread的兼容性更好,天生就是跨平台的,同一份代码在单片机、windows、linux……上都能跑,不管用什么C/C++编译器...
no。。。虽然都转换为状态机,但是很显然它们的理念是不同的,所以并不是同一个玩意,不能随意混淆。。。
并行是为了让多个程序共同工作,而异步则是可以让程序更灵活地决定程序的流程。。。
这里讲的是概念,并不是讲实用性,我承认protothread很实用,不过也就几个宏而已吧。。。
引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论
miaowrx
7年11个月前 IP:四川
821216

异步和并行肯定是同一个玩意儿。穿了一个马甲而已, 异步肯定是并行的,同一个时间在执行不同的流程,虽然流程不同,但同属于一个程序、一个逻辑。谁说并行一定是多个核心执行同一款代码?

引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论
miaowrx
7年11个月前 IP:四川
821217

异步只是并行的一种管理方式而已。

引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论
acmilan作者
7年10个月前 修改于 7年10个月前 IP:四川
821249

引用 miaowrx : 异步和并行肯定是同一个玩意儿。穿了一个马甲而已, 异步肯定是并行的,同一个时间在执行不同的流程,虽然流程不同,但同属于一个程序、一个逻辑。谁说并行一定是多个核心执行同一款代码?

(查了一下,并发[concurrency]才是状态机,并行[parallelism]不是,纠正过来)

事实上并发和异步都是状态机的马甲。。。

引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论

想参与大家的讨论?现在就 登录 或者 注册

所属专业
上级专业
同级专业
acmilan
进士 学者 笔友
文章
461
回复
2934
学术分
4
2009/05/30注册,5年2个月前活动
暂无简介
主体类型:个人
所属领域:无
认证方式:邮箱
IP归属地:未同步
文件下载
加载中...
{{errorInfo}}
{{downloadWarning}}
你在 {{downloadTime}} 下载过当前文件。
文件名称:{{resource.defaultFile.name}}
下载次数:{{resource.hits}}
上传用户:{{uploader.username}}
所需积分:{{costScores}},{{holdScores}}下载当前附件免费{{description}}
积分不足,去充值
文件已丢失

当前账号的附件下载数量限制如下:
时段 个数
{{f.startingTime}}点 - {{f.endTime}}点 {{f.fileCount}}
视频暂不能访问,请登录试试
仅供内部学术交流或培训使用,请先保存到本地。本内容不代表科创观点,未经原作者同意,请勿转载。
音频暂不能访问,请登录试试
支持的图片格式:jpg, jpeg, png
插入公式
评论控制
加载中...
文号:{{pid}}
投诉或举报
加载中...
{{tip}}
请选择违规类型:
{{reason.type}}

空空如也

加载中...
详情
详情
推送到专栏从专栏移除
设为匿名取消匿名
查看作者
回复
只看作者
加入收藏取消收藏
收藏
取消收藏
折叠回复
置顶取消置顶
评学术分
鼓励
设为精选取消精选
管理提醒
编辑
通过审核
评论控制
退修或删除
历史版本
违规记录
投诉或举报
加入黑名单移除黑名单
查看IP
{{format('YYYY/MM/DD HH:mm:ss', toc)}}