单片机学习程序员

嵌入式开发系列教程(六) 并发模型之协程

2017-04-26  本文已影响111人  qianlihu
void LedFlash()
{
    LedOn();
    delay(3);    //点亮3s 我们希望此刻CPU能够处理其他任务,而不是忙等待
    LedOff();
    delay(5);     //熄灭5s 我们希望此刻CPU能够处理其他任务,而不是忙等待
    LedOn();
    delay(5);     //点亮5s 我们希望此刻CPU能够处理其他任务,而不是忙等待
    LedOff();
    delay(3);     //熄灭3s 我们希望此刻CPU能够处理其他任务,而不是忙等待
}

我们希望在执行delay函数的时候,CPU能够处理其他任务,而不是忙等待,如果有OS的协助的话,这很容易实现,没有OS的话,我们前面用例回调函数+状态机的方法实现了上述功能,这里我们再介绍协程的方法,协程的概念其实很早就提出了。只不过最近由于并发编程的兴起,被更多的人关注,又火起来了。
在嵌入式领域,Contiki就借助了协程的概念。

协程模型

我们先来看一下回调模型中的写法

static int STATE = 0x10;

void LedFlash()
{
    switch(STATE){
        case 0x10:
            LED = ON;  //点亮LED灯
            STATE = 0x11;  //切换状态
            setTimerEvent(3000,LedFlash);
            break;
        case 0x11:
            LED = OFF;  
            STATE = 0x20;  //切换状态
            setTimerEvent(5000,LedFlash);
            break;
        case 0x20:
            LED = ON;  
            STATE = 0x21;  //切换状态
            setTimerEvent(5000,LedFlash);
            break;
        case 0x21:
            LED = OFF; 
            STATE = 0x10;  //切换状态
            setTimerEvent(3000,LedFlash);
            break;
    }
}

对于协程模型,我们希望看到这样的代码。

void LedFlash()
{
    LedOn();
    setTimerEvent(3000,LedFlash); //定时3s,3s后定时器系统再调用LedFlash函数
    return;     //函数返回,,这样的话,便不占用CPU资源
    LedOff();  //3s后,我们希望LedFlash函数直接执行这一行  
    setTimerEvent(5000,LedFlash);  
    return; 
    LedOn();    
    setTimerEvent(5000,LedFlash);
    return;
    LedOff();   
    setTimerEvent(3000,LedFlash);
    return;
}

很明显,C语言的return语义不能帮我们做到这一点,我们引入一个关键字yield,yield的功能便是使得函数返回,并且再下一次调用中,从该行的下一行开始执行代码。

void LedFlash()
{
    LedOn();
    setTimerEvent(3000,LedFlash); //定时3s,3s后定时器系统再调用LedFlash函数
    yield;     //yield,让出CPU
    LedOff();  //3s后,我们希望LedFlash函数直接执行这一行  
    setTimerEvent(5000,LedFlash);  
    yield;     //yield,让出CPU 
    LedOn();    
    setTimerEvent(5000,LedFlash);
    yield;     //yield,让出CPU
    LedOff();   
    setTimerEvent(3000,LedFlash);
    yield;     //yield,让出CPU
}

那怎么实现这种功能呢?

void LedFlash()
{
    static int state = 0; //注意 state 为什么要用static 修饰
    switch(state){
        case 0:
           LedOn();
           setTimerEvent(3000,LedFlash); //定时3s,3s后定时器系统再调用LedFlash函数
           state = 1;
           break;
        case 1:
           LedOff();  //3s后,我们希望LedFlash函数直接执行这一行  
           setTimerEvent(5000,LedFlash); 
           state = 2;
           break;
        case 2:
           LedOn();    
           setTimerEvent(5000,LedFlash);
           state = 3;
           break;
        case 3:
         LedOff();   
           setTimerEvent(3000,LedFlash);
           state = 4;       //注意此处有bug,可以暂时忽略
           break;
    }
}

现在我们发现此处代码其实和回调模型中的代码逻辑几乎是一样的。那这跟协程,跟yield有什么关系呢?

在上述代码中,state状态被标记为[0,1,2,3]了,其实state只要是四个不同的整数来代表不同的状态就行。 我们修改一下代码

void LedFlash()
{
    static int state = 0; //注意 state 为什么要用static 修饰
    switch(state){
        case 0:
           LedOn();
           setTimerEvent(3000,LedFlash); //定时3s,3s后定时器系统再调用LedFlash函数
           state = __LINE__ + 2;  //state 状态切换为下个case的入口
           return;
        case __LINE__:   //此处__LINE__ = 两行前的 __LINE__ + 2
           LedOff();  //3s后,我们希望LedFlash函数直接执行这一行  
           setTimerEvent(5000,LedFlash); 
           state = __LINE__ + 2;  //state 状态切换为下个case的入口
           return;
        case  __LINE__:   //此处__LINE__ = 两行前的 __LINE__ + 2
           LedOn();    
           setTimerEvent(5000,LedFlash);
           state = __LINE__ + 2;  //state 状态切换为下个case的入口
           return;
        case __LINE__:   //此处__LINE__ = 两行前的 __LINE__ + 2
         LedOff();   
           setTimerEvent(3000,LedFlash);
         state = __LINE__ + 2;  //此处bug请自动忽略
           return;
    }
}

现在大家肯定发现了代码中有很多重复的地方,我们提炼一下

void LedFlash()
{
    static int state = 0; 
    switch(state){
        case 0:          参考 下文中 Begin()宏
           ;  // 执行一条空语句,无意义  其实是Begin()后面的分号
           LedOn();
           setTimerEvent(3000,LedFlash);
           state = __LINE__ ; reutrn; case __LINE__: //参考下文中Yield()宏
           ;  // 执行一条空语句,无意义  其实是Yield()后面的分号
           LedOff(); 
           setTimerEvent(5000,LedFlash); 
           state = __LINE__ ; reutrn; case __LINE__:
           ;  // 执行一条空语句,无意义
           LedOn();    
           setTimerEvent(5000,LedFlash);
           state = __LINE__ ; reutrn; case __LINE__:
           ;  // 执行一条空语句,无意义
           LedOff();   
           setTimerEvent(3000,LedFlash);
           //请忽略下面的逻辑
    }   //参考下文中 End()宏
}

在这里的代码中,我们把state=__LINE__ case __LINE__放到了一行,这样便少了一次加法操作。

我们用宏提炼一下上述代码

#define Begin()   static int state=0; switch(state) { case 0:
#define Yield()   state=__LINE__; return; case __LINE__: 
#define End() }
void LedFlash()
{
    Begin();
    LedOn();
    setTimerEvent(3000,LedFlash); //定时3s,3s后定时器系统再调用LedFlash函数
    Yield();     //yield,让出CPU,对应代码我们可以看到是return返回
    LedOff();  //3s后,LedFlash函数会根据state直接执行这一行  
    setTimerEvent(5000,LedFlash);  
    Yield();     //yield,让出CPU 
    LedOn();    
    setTimerEvent(5000,LedFlash);
    Yield();     //yield,让出CPU
    LedOff();   
    setTimerEvent(3000,LedFlash);
    Yield();     //yield,让出CPU
    End();

}

其实对于Yield(),我们可以用一种更安全的宏包装。

#define Yield() do { state=__LINE__; return; case __LINE__:; } while (0)

现在对比回调模型中的代码,我们发现,代码逻辑比较清晰了。

缺陷

这是一个非常简单的协程模型,其实就是Contiki的作者写的。Contiki也是用的这个模型。有几个缺陷

文章参考:
一个“蝇量级” C 语言协程库

这是一个免费,开源的教程,如果你喜欢可以转发,也可以打赏奖励。 欢迎关注微信公众号小站练兵

上一篇下一篇

猜你喜欢

热点阅读