栏目分类:
子分类:
返回
终身学习网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
终身学习网 > IT > 软件开发 > 后端开发 > C/C++/C#

c语言协程[1]

C/C++/C# 更新时间:发布时间: 百科书网 趣学号

“协程的本质是利用程序语言语法来实现逻辑上的多任务的编程;”

很多年前,我在小单片机上一直想跑操作系统,奈何Flash和RAM一直没有合适的;后来想自己怼个操作系统,结果拖延症犯了,到现在也无果(rtt,freertos真香);后来一直在想有啥更好的方式去写代码,想着程序也就是"时间+内存+状态",然后得到了状态机;

状态机

对于单片机程序来说,基本就是一个状态来带动另个状态,在c语言中实现状态切换的基本就是switch了,下面是简单的实现;

while(1){
    switch(我是状态){
        case 状态1:
            下个状态是2;
            break;
        case 状态2:
            下个状态是4;
            break;
        case 状态3:
            这里结束了;
            break;
        case 状态4:
            下个状态是3;
            break;
    }
}

不同的状态执行不同的代码块,这时可以将时间也作为一个状态变量加入,这样就实现了一个"前后台+时间片轮转"的代码架构; 但是状态越多就会越复杂,比如我要延时10ms去运行A在延时500ms去运行B这个状态机实现就会麻烦很多:

//设定时器时1ms运行一次,运行回调时全局变量时基g_TickCount++;
int main(void)
{
    int State = 0;  //设State是状态,默认等于0;
    int Time;
    //状态机
    while(1){
        switch(State){
            //准备10ms延时
            case 0:
                Time = g_TickCount+10;  //要延时10ms,时基+10
                State = 1;              //下个状态
                break;
            //延时10ms
            case 1:
                if(g_TickCount >= Time){    //定时器在一直运行,g_TickCount在10ms后会超过Time,
                    State = 2;              //这里状态一直不变,等于延时了10ms,时间到了则立刻切换;
                }
                break;
            //执行A,并准备下个延时时间的数据
            case 2:
                
                Time = g_TickCount+500; //要延时500ms,时基+500
                State = 3;              //下个状态
                break;
            //延时500
            case 3:
                if(g_TickCount >= Time){    //定时器在一直运行,g_TickCount在500ms后会超过Time,
                    State = 4;              //这里状态一直不变,等于延时了500ms,时间到了则立刻切换;
                }
                break;
            //执行B
            case 4:
                
                break;
        }
    }
}

到这里大家可以看出,在while下的switch是可以当做独立的一个任务,若是我要在这个代码里多跑几个"延时10ms去运行A在延时500ms去运行B",那可以多放几个switch,每个switch任务相互独立,宏观上看就像多个任务同时运行;

协程

终于终于到正题了, 协程就是优化了上面的状态机; 其实就是利用程序语言语法来实现逻辑上的多任务的编程; 网上有大神说"任何编程规范,坚持牺牲算法清晰度来换取语法清晰度的,都应该重写.",虽不明但觉厉;

用switch状态机最麻烦的是状态的定义,状态需要每个不同且需要手动去写,任务程序大了后状态量会越来越臃肿,程序结构也会越来越不清晰,那有啥办法解决这个问题?

在 ANSI C 中预定义了很多个宏,其中有一个宏"__LINE__“,替换后是当前文件下”__LINE__“所在的行数,这样就使”__LINE__“在每个文件中有了唯一性; 用”__LINE__"作为状态,则解决了状态的的唯一性; 剩下的就是要咋去解决手动定义下个状态的问题了,我们来看一段代码;

typedef unsigned short ushort;  //快捷定义一个无符号16位的类型
int main(void)
{
    static ushort BP1 = 0;       //定义一个全局变量,作为任务1断点
    static ushort BP2 = 0;       //定义一个全局变量,作为任务2断点
//===
    while(1){
        //任务1
        switch(BP1){
            case 0:
                
                BP1 = __LINE__; goto GOTO_End1; case __LINE__:  //这里必须一行,这样BP指向和状态是同个行号
                
                BP1 = __LINE__; goto GOTO_End1; case __LINE__:  //这里必须一行,这样BP指向和状态是同个行号
                
                BP1 = __LINE__; goto GOTO_End1; case __LINE__:  //这里必须一行,这样BP指向和状态是同个行号
                
        }
        GOTO_End1:      //这是一个goto跳转的标签(任务1)
        //任务2
        switch(BP2){
            case 0:
                
                BP2 = __LINE__; goto GOTO_End2; case __LINE__:  //这里必须一行,这样BP指向和状态是同个行号
                
                BP2 = __LINE__; goto GOTO_End2; case __LINE__:  //这里必须一行,这样BP指向和状态是同个行号
                
                BP2 = __LINE__; goto GOTO_End2; case __LINE__:  //这里必须一行,这样BP指向和状态是同个行号
                
        }
        GOTO_End2:      //这是一个goto跳转的标签(任务2)
    }
}

上面代码用一个全局变量来保存当前"__LINE__“,然后goto跳出任务,等待下个周期调用任务又回到了”__LINE__"继续下个代码段处理,就像在代码中自动设置了可恢复状态的断点;

在代码中也可以看出设置断点跳出的代码都是相同,switch的代码也是相同的;这时候我们就可以更加简化的用宏定义封装代码:

    //协程:任务的开始
    #define _COR_Start(BP)  
        switch((BP)){       
            case 0:

    //协程:任务的结束
    #define _COR_End()      
        }                   
        GOTO_End:;

    //协程:设置一个断点并跳出
    #define  _COR_SetBPBreak(BP)    
        (BP) = __LINE__; goto GOTO_End; case __LINE__:

这样一个简单的协程雏形就出来了,我们优化下之前的代码,用协程宏来替代:

typedef unsigned short ushort;  //快捷定义一个无符号16位的类型

//任务1
void Task1(void)
{
    static ushort BP = 0;   //定义一个全局变量,作为任务断点
//===
    _COR_Start(BP);         //任务开始

    
    _COR_SetBPBreak(BP);    //设置断点并跳出
    
    _COR_SetBPBreak(BP);    //设置断点并跳出
    
    _COR_SetBPBreak(BP);    //设置断点并跳出
    

    _COR_End(BP);           //任务结束
}

//任务2
void Task2(void)
{
    static ushort BP = 0;   //定义一个全局变量,作为任务断点
//===
    _COR_Start(BP);         //任务开始

    
    _COR_SetBPBreak(BP);    //设置断点并跳出
    
    _COR_SetBPBreak(BP);    //设置断点并跳出
    
    _COR_SetBPBreak(BP);    //设置断点并跳出
    

    _COR_End(BP);           //任务结束
}

int main(void)
{
    while(1){
        Task1();    //任务1
        Task2();    //任务2
    }
}


可以看到用协程代码替换后整个代码结构就清晰整齐了很多;

基础协程完成,后面就要处理时间了;
同时还有一个问题,switch是轮询跳转的,协程跳转多了后性能会降低,代码越执行到后面,性能越低;当然这个性能时间微乎其微完全可以忽略,但是追求极致的我们肯定还是要纠结一把;

未完待续

转载请注明:文章转载自 www.051e.com
本文地址:http://www.051e.com/it/1033236.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 ©2023-2025 051e.com

ICP备案号:京ICP备12030808号