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

CaptureAsyncCpp.png
// 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>

【06-01更新】
对于上面的程序,如果程序调用了异步函数并没有立即await同一个函数,而是await别的去了,那么将会导致ctx被提前delete。
更新内容:增加abandoned标志,设置此标志表示放弃await,消息循环将自动回收ctx,否则需手动回收。
// 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>
[ 修改于10年6个月前 - 2016/06/01 17:43:42 ]
10年6个月前IP:浙江
820232
1楼
手上有个FreeRTOS的模拟器是用PThread实现的
引用
评论
10年6个月前IP:陕西
820242
2楼
感觉这样的异步比JavaScript中的异步更不自然。
如果用Lua,可以让异步代码形式上看起来是同步的,但实际上是异步执行。这样的代码人更容易理解。
引用
评论
acmilan作者
10年6个月前IP:四川
820245
3楼
引用 金星凌日:
感觉这样的异步比JavaScript中的异步更不自然。
如果用Lua,可以让异步代码形式上看起来是同步的,但实际上是异步执行。这样的代码人更容易理解。
实际上,javascript中的基于闭包的异步编程重新回到了面条式编程,lua或者c# 5.0或es6的async/await(coroutine)机制对异步编程最大的贡献是实现了结构化异步编程。
引用
评论
10年6个月前IP:陕西
820246
4楼
引用 acmilan:
实际上,javascript中的基于闭包的异步编程重新回到了面条式编程,lua或者c# 5.0或es6的async/await(coroutine)机制对异步编程最大的贡献是实现了结构化异步编程。
我记得类似的东西在Scheme里也出现过。
引用
评论
10年6个月前IP:广东
820438
5楼
不友好……
建议搜索“protothread”了解一下。这是uip带的。uip和lwip的作者出品
引用
评论
acmilan作者
10年6个月前修改于 10年6个月前IP:四川
820468
6楼
引用 amo:
不友好……
建议搜索“protothread”了解一下。这是uip带的。uip和lwip的作者出品
这个只是一个原型程序(演示程序),并不是实用程序。。。(实用需要语法糖或宏的支持)

protothread主要是为了并行编程而设计的,并没有针对异步串行编程设计的队列和上下文管理。。。
对异步串行编程友好的语言,需要具有async/await机制,上面已经说过了,比如c# 5.0、javascript es6、lua等。。。
引用
评论
10年6个月前IP:广东
820506
7楼
引用 acmilan:
这个只是一个原型程序(演示程序),并不是实用程序。。。(实用需要语法糖或宏的支持)

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

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

引用
评论
10年6个月前IP:四川
821217
10楼

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

引用
评论
acmilan作者
10年6个月前修改于 10年6个月前IP:四川
821249
11楼

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

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

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

引用
评论
笔记
{{note.content}}
{{n.user.username}}
{{fromNow(n.toc)}} {{n.status === noteStatus.disabled ? "已屏蔽" : ""}} {{n.status === noteStatus.unknown ? "正在审核" : ""}} {{n.status === noteStatus.deleted ? '已删除' : ''}}
  • 编辑
  • 删除
  • {{n.status === 'disabled' ? "解除屏蔽" : "屏蔽" }}
我也是有底线的