ProtoThread 协程复习笔记

近一年没用 ProtoThread 了,忘得差不多了。这次搞硬件密码器重新学习了下,同时记录下来笔记,方便下次使用。

ProtoThread 是一个资源占用极少的多线程协程库,可以在51、avr等资源极其有限的硬件上面运行。

ProtoThread 有几种实现方式,这里只考虑存c switch 实现。这个实现其实就是用1byte存放当前执行到的位置,下次恢复这个线程的时候通过 switch 跳转到上次执行的位置继续执行。每个线程就只占用2byte存放当前位置(当前代码行号)即可。伪代码:

 
//线程结构体
struct pt {
  unsigned short lc;   // 当前线程执行到的位置(行号)
};
PT_THREAD(f1(struct pt *pt))
// 展开宏后的代码
// char f1(struct pt *pt)
{
    // 这里可以写每次任务切换进来都执行的代码    
    //  注意,只有静态变量才能正确的保存,非静态变量在切换协程时会丢失
    // 同样,静态变量的限制使得一个函数不能多协程同时执行,局部变量会冲突。
    static int a;

   PT_BEGIN(pt);  // 准备协程环境
    // 展开宏后的代码:
    // char PT_YIELD_FLAG = 1;
   //  switch(pt.lc) { case 0;
   // 等待至条件达成
    // 可以看到,是通过记录当前执行位置后直接退出函数来实现的让出cpu。
    // 恢复执行时通过 switch 的 case 跳转回上次执行的位置来实现的恢复执行。
    PT_WAIT_UNTIL(pt, 条件);
    // 展开后的代码:
    /*
  do {
    (pt)->lc =  __LINE__;
     case __LINE__:;
    if(!(condition)) {
      return PT_WAITING;  
    }
  } while(0)
   */
   PT_END(pt); // 结束协程环境
    // 展开后的代码:
    // };
    // PT_YIELD_FLAG = 0;
    // pt.lc=0;
    // return PT_ENDED;
}
void main()
{
    struct pt p1;    // 线程1
    struct pt p2;   // 线程2
   //初始化线程
    PT_INIT(&p1); // 其实就是 p1.lc = 0
    PT_INIT(&p2); 
   while(1)
    {
      /// 启动两个线程
      f1(&p1); 
      f1(&p2);        
    }
}

这种实现的几个特点:
• 纯c实现,无硬件依赖,方便移植
• 不能在函数内再次使用switch
• 资源占用极少,每线程仅占用2字节
• 支持阻塞操作,没有栈的切换
• 运行在单一c函数中,无法跨函数是用。弥补措施是被调用函数也写成单独的Protothread
• 线程切换时不保存局部变量,弥补措施是使用静态局部变量,造成单个函数不能多线程同时执行。另一个弥补措施是实现自己的 pt 结构,将需要保存的局部变量保存到 pt 结构里面。

附几个函数:
PT_INIT(pt) 初始化任务变量,只在初始化函数中执行一次就行
PT_BEGIN(pt) 启动任务处理,放在函数开始处
PT_END(pt) 结束任务,放在函数的最后
PT_WAIT_UNTIL(pt, condition) 等待某个条件(条件可以为时钟或其它变量,IO等)成立,否则直接退出本函数,下一次进入本 函数就直接跳到这个地方判断
PT_WAIT_WHILE(pt, cond) 和上面一个一样,只是条件取反了
PT_WAIT_THREAD(pt, thread) 等待一个子任务执行完成
PT_SPAWN(pt, child, thread) 新建一个子任务,并等待其执行完退出
PT_RESTART(pt) 重新启动某个任务执行
PT_EXIT(pt) 任务后面的部分不执行,直接退出重新执行
PT_YIELD(pt) 锁死任务
PT_YIELD_UNTIL(pt, cond) 锁死任务并在等待条件成立,恢复执行
在pt中一共定义四种线程状态,在任务函数退出到上一级函数时返回其状态
PT_WAITING 等待
PT_EXITED 退出
PT_ENDED 结束
PT_YIELDED 锁死

(下面的是定时器,该宏是 http://www.cnblogs.com/xiaowuyi/p/4319720.html 写的,用之前请在#include “pt.h” 的前面,前面啊!加上一句#define PT_USE_TIMER)

先说明一下,下面的定时不一定完全准确的,可能会有点点的误差,可能偏后。如果遇上了很烦的任务,有可能会使延时延后。但是正常情况下,直接用就好了。

如果要很精确的延时,请用delay语句或者计时器,但是,绝大多数情况下,绝大多数情况!绝大多数情况!请用下面的语句代替delay延时!这样才能把CPU让给别的任务使用。

PT_TIMER_DELAY(pt,延时毫秒数);
字面上的意思,不用多说了吧?最大值约为49.7天,估计没人会延时辣么久吧……

PT_TIMER_MICRODELAY(pt,延时微秒数);
字面上的意思,不用多说了吧?注意,最小精度与arduino的版本有关,与micros()有精度一致。

PT_TIMER_WAIT_TIMEOUT(pt,条件,毫秒数);
如果条件成立了,或者超时了,就继续运行,否则切换任务。

要用的话,请在#include “pt.h”前面加上一句 #define PT_USE_SEM

首先要创建一个信号量,这个一定是全局变量:
static struct pt_sem 信号量名;

接着请在setup()函数里面给它初始化:
PT_SEM_INIT(&信号量名,数量);
信号量名前面有个&,别忘了。数量就相当于停车场的总车位数。

然后要用啦。任务要停一辆车进去:
PT_SEM_WAIT(pt,&信号量名);
信号量名前面有个&,别忘了。一个语句只能停一辆车,土豪好多车就用多次。

任务要开一辆车出来:
PT_SEM_SIGNAL(pt,&信号量名);
信号量名前面有个&,别忘了。用一次出一辆。

参考:
• http://dunkels.com/adam/pt/
• Protothread机制文档 http://blog.csdn.net/tietao/article/details/8507455
• 一个“蝇量级” C 语言协程库 http://coolshell.cn/articles/10975.html
• ProtoThreads http://blog.csdn.net/utopiaprince/article/details/6041385

  WordPress › 错误