“协程的本质是利用程序语言语法来实现逻辑上的多任务的编程;”
前文完成了一个简单switch协程,但是还是有优化的空间,让程序更快的跳转出入任务;
goto
网上对c语言goto的使用,褒贬不一,但是存在必是有意义的,用的好是真的好用;
goto是无条件跳转,直接跳转到标号位置,这个和switch中跳转类似但是是不做判断直接跳转; c语言中,switch的标号是一个常量值,可以轮询判断变量去跳转; 但是在goto中它的标号只是一个代码标志, 并不能把它作为一个常量赋给变量,goto也不能去跳转一个变量;
当然以上只是"ANSI-C"的goto特性; 但是我们还有"GNU-C",大名鼎鼎的gcc编译器就是基于"GNU-C"标准的; 我们MCU端编译器也都支持,下图是MDK的设置,其他编译器请自行百度;
"GNU-C"中 goto 增加了一个重要特性(Labels as Values):
可以用 && 将 goto 的标号赋值给 void* 类型变量; 同时goto可以用此变量跳转到对应标号;
使用方法参考下面代码:
int main(void)
{
void* BP;
int i=0;
//===
BP = &&GOTO_End; //将"GOTO_End"标号保存到BP;
while(1){
if(i++ >= 5){ //i累加到5的时候执行goto;
goto *BP; //跳转的 BP 是一个变量,指向了"GOTO_End",等于"goto GOTO_End";
}
}
GOTO_End: //goto跳转到这里;
return(0);
}
goto 协程
有了"GNU-C"中 goto 特性,我们就可以实现 goto协程 了;
下面是宏定义封装的goto协程代码:
typedef void* TypeCOR_BP; //定义一个协程断点类型(标号)
//协程-断点
#define _COR_BP2(S1, S2) _##S1##S2
#define _COR_BP(S1, S2) _COR_BP2(S1, S2)
//协程-开始
#define _COR_Start(BP) \
do{ \
if(BP != NULL){ \
goto *BP; \
} \
}while(0)
//协程-结束
#define _COR_End(BP) \
GOTO_End:;
//协程-设置一个断点并跳出
#define _COR_SetBPBreak(BP) \
do{ \
/*保存并设置断点(函数名+行号)然后跳出*/ \
(BP) = &&_COR_BP(__func__, __LINE__); goto _XCCOR_GOTO_End; _COR_BP(__func__, __LINE__):; \
}while(0)
说明:
- 协程-断点:
在c语言中 ## 是用来连接2个字符串, 用 __func__ 和 __LINE__ 2个宏及一个’_'来拼接唯一的标号;
但是 __func__ 和 __LINE__ 也是宏定义, 所以需要2次才能完整展开;
第一次用"COR_BP"展开 __func_ 和 __LINE__; 第二次用"_COR_BP2" 的 ## 拼接;
注:
- __func__ :表示当前代码所在文件的文件名;
- '_'是为了防止数字开头的文件,导致标号不符合c语言规范;
- 协程-开始:
"BP"是一个指针,初始"BP"指向"NULL"表示任务开始;- 协程-结束:
协程结束,只是用作跳出;- 协程-设置一个断点并跳出:
和switch一样,将goto标号保存,并设置,然后跳出协程;
因为"ANSI协程"和"GNU协程"宏定义是相同的,所以协程任务代码大部分也是相同的:
//任务1
void Task1(void)
{
static TypeCOR_BP BP = NULL; //定义一个全局变量,作为任务断点
//===
_COR_Start(BP); //任务开始
/*任务1-处理代码1*/
_COR_SetBPBreak(BP); //设置断点并跳出
/*任务1-处理代码2*/
_COR_SetBPBreak(BP); //设置断点并跳出
/*任务1-处理代码3*/
_COR_SetBPBreak(BP); //设置断点并跳出
/*任务1-处理代码4*/
_COR_End(BP); //任务结束
}
//任务2
void Task2(void)
{
static TypeCOR_BP BP = NULL; //定义一个全局变量,作为任务断点
//===
_COR_Start(BP); //任务开始
/*任务2-处理代码1*/
_COR_SetBPBreak(BP); //设置断点并跳出
/*任务2-处理代码2*/
_COR_SetBPBreak(BP); //设置断点并跳出
/*任务2-处理代码3*/
_COR_SetBPBreak(BP); //设置断点并跳出
/*任务2-处理代码4*/
_COR_End(BP); //任务结束
}
int main(void)
{
while(1){
Task1(); //任务1
Task2(); //任务2
}
}
/* 运行结果是:
* 任务1-处理代码1
* 任务2-处理代码1
* 任务1-处理代码2
* 任务2-处理代码2
* 任务1-处理代码3
* 任务2-处理代码3
* 任务1-处理代码4
* 任务2-处理代码4
* 任务1-处理代码4
* 任务2-处理代码4
* .....
*/
2个基础的协程讲完了后面我们要给协程加上时间控制,以来应对更复杂的代码逻辑;
未完待续
-
协程代码,码云链接
-
相关链接: