protothreads协程浅析 发表于 2024-11-18 | 更新于 2024-11-19
| 字数总计: 1.6k | 阅读时长: 7分钟 | 阅读量:
protothreads是一个C语言对协程的巧妙精简实现, 可应用于资源不足的单片机系统。 对于不移植嵌入式操作系统的单片机程序, 可替代原先复杂的for大循环实现, 应用协程可优化程序结构, 提高可读性。 (用protothreads在C51和STM32F1片子上应用,没有问题)
protothreads浅析 协程是一种轻量级的线程,通过用户态调度和主动交出控制权的方式,提高了程序的性能和响应速度。protothreads仓库地址
使用协程的优势: 第一个是协程的执行效率高, 因为子程序切换不是线程切换, 而是由程序自身控制。因此没有线程切换的开销, 和多线程相比, 线程数量越多, 协程的性能优势就越明显。 第二个是不需要多线程的锁机制, 因为只有一个线程, 也不存在同时写变量冲突, 在协程中控制共享资源不加锁, 只需要判断撞他就好, 所以执行效率比多线程高很多。
数据结构 protothreads提供了三种实现方式,可通过宏来配置。 默认是switch/case实现方式, 其次可通过 PT_USE_SETJMP 或 PT_USE_GOTO来切换实现方式。
1 2 3 4 5 6 7 8 9 10 /* Protothread status values */ #define PT_STATUS_BLOCKED 0 // 阻塞状态 #define PT_STATUS_FINISHED -1 // 结束状态 #define PT_STATUS_YIELDED -2 // 让出状态 /* Helper macros to generate unique labels */ #define _pt_line3(name, line) _pt_##name##line #define _pt_line2(name, line) _pt_line3(name, line) #define _pt_line(name) _pt_line2(name, __LINE__)
switch/case实现方式 基于switch/case和行号实现, 每个协程由pt_begin开始, 先放置一个switch语句, 然后再协程控制点添加case标签。协程执行的时候, 根据记录的行号跳转到对应的case分支。
限制: 1.每行不允许超过一个标签 2.不能保存本地变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 /* * Local continuation based on switch/case and line numbers. * * Pros: works with any C99 compiler, most simple implementation. * Cons: doesn't allow more than one label per line, doesn't preserve local * variables. */ struct pt{ int label; //行号 int status; //状态 }; #define pt_init() \ { .label = 0, .status = 0 } #define pt_begin(pt) \ switch ((pt)->label) { \ case 0: #define pt_label(pt, stat) \ do { \ (pt)->label = __LINE__; \ (pt)->status = (stat); \ case __LINE__:; \ } while (0) #define pt_end(pt) \ pt_label(pt, PT_STATUS_FINISHED); \ }
goto实现方式 基于goto和标签引用实现, 每个协程由pt_begin开始, 通过记录的标签地址, 进行跳转。
限制: 1.需要GCC或Clang编译器 2.不能保存本地变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 /* * Local continuation based on goto label references. * * Pros: works with all control sturctures. * Cons: requires GCC or Clang, doesn't preserve local variables. */ struct pt { void *label; int status; }; #define pt_init() \ { .label = NULL, .status = 0 } #define pt_begin(pt) \ do { \ if ((pt)->label != NULL) { \ goto *(pt)->label; \ } \ } while (0) #define pt_label(pt, stat) \ do { \ (pt)->status = (stat); \ _pt_line(label) : (pt)->label = &&_pt_line(label); \ } while (0) #define pt_end(pt) pt_label(pt, PT_STATUS_FINISHED)
setjmp方式 基于系统函数setmp/longjmp实现, 每个协程由pt_begin开始,通过setjmp恢复寄存器数据,继续执行; 然后再协程的控制点保存寄存器数据, 让出控制权限。
限制: 1.慢 2.可能会擦除全局寄存器变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 /* * Local continuation that uses setjmp()/longjmp() to keep thread state. * * Pros: local variables are preserved, works with all control structures. * Cons: slow, may erase global register variables. */ #include <setjmp.h> struct pt { jmp_buf env; int isset; int status; }; #define pt_init() \ { .isset = 0, .status = 0 } #define pt_begin(pt) \ do { \ if ((pt)->isset) { \ longjmp((pt)->env, 0); \ } \ } while (0) #define pt_label(pt, stat) \ do { \ (pt)->isset = 1; \ (pt)->status = (stat); \ setjmp((pt)->env); \ } while (0) #define pt_end(pt) pt_label(pt, PT_STATUS_FINISHED)
Core protothreads API 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 /* * Core protothreads API */ #define pt_status(pt) (pt)->status // 协程等待函数 #define pt_wait(pt, cond) \ do { \ pt_label(pt, PT_STATUS_BLOCKED); \ //在当前行号位置记录标签 if (!(cond)) { \ //后续每次进入函数,会立即跳转过来判断条件是否满足 return; \ //不满足条件,直接退出 } \ //满足条件,继续执行 } while (0) // 协程让出控制权限函数 #define pt_yield(pt) \ do { \ pt_label(pt, PT_STATUS_YIELDED); \ //在当前行号位置记录标签 if (pt_status(pt) == PT_STATUS_YIELDED) { \ (pt)->status = PT_STATUS_BLOCKED; \ return; \ //仅让出一次控制权限 } \ } while (0) // 协程退出函数 #define pt_exit(pt, stat) \ do { \ pt_label(pt, stat); \ return; \ } while (0) // 协程忙等直接条件满足 #define pt_loop(pt, cond) \ for (int _intr = 0; _intr == 0;) \ if (1) { \ pt_label(pt, PT_STATUS_BLOCKED); \ //在当前行号位置记录标签 if (!(cond)) { \ //测试条件是否满足 break; \ //满足条件退出 } \ goto _pt_line(body); \ //不满足条件,goto到指定标签位置,继续测试条件是否满足 } else \ while (1) \ if (1) { \ _intr = 1; \ break; \ } else \ while (1) \ if (1) { \ return; \ } else \ _pt_line(body) :
protothreads应用 全局变量方式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 #include "includes.h" typedef pt_queue(int, 32) QUEUE; static QUEUE queue; static QUEUE *q = &queue; static uint32_t timer_tick = 0; static uint32_t led_interval = 3; void consume_task(struct pt *task); void product_task(struct pt *task); static volatile int flag = 0; static volatile int data = 0; int main(void) { struct pt pt_consume = pt_init(); struct pt pt_product = pt_init(); queue.r = 0; queue.w = 0; pt_timer_reset(&timer_tick); while(1) { consume_task(&pt_consume); product_task(&pt_product); if(pt_timer_expired(&timer_tick, led_interval)) { printf("%u: toggle led!\n", getSystemTick()); pt_timer_reset(&timer_tick); } } } void consume_task(struct pt *pt) { static int *ptr = NULL; static uint32_t timer = 0; pt_begin(pt); /* 初始化一次 */ pt_timer_reset(&timer); for(;;) { pt_wait(pt, flag != 0); printf("consume data: %d!\n", data); flag = 0; pt_yield(pt); } pt_end(pt); } void product_task(struct pt *pt) { static uint32_t timer = 0; pt_begin(pt); /* 初始化一次 */ pt_timer_reset(&timer); for(;;) { pt_wait(pt, flag == 0); if(pt_timer_expired(&timer, 8)) { data++; printf("product data: %d!\n", data); flag = 1; pt_timer_reset(&timer); } pt_yield(pt); } pt_end(pt); }
队列方式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 #include "includes.h" typedef pt_queue(int, 32) QUEUE; static QUEUE queue; static QUEUE *q = &queue; static uint32_t timer_tick = 0; static uint32_t led_interval = 3; void consume_task(struct pt *task); void product_task(struct pt *task); static volatile int flag = 0; static volatile int data = 0; int main(void) { struct pt pt_consume = pt_init(); struct pt pt_product = pt_init(); queue.r = 0; queue.w = 0; pt_timer_reset(&timer_tick); while(1) { consume_task(&pt_consume); product_task(&pt_product); // 刷新LED状态 if(pt_timer_expired(&timer_tick, led_interval)) { printf("%u: toggle led!\n", getSystemTick()); pt_timer_reset(&timer_tick); } } } void consume_task(struct pt *pt) { static int *ptr = NULL; static uint32_t timer = 0; pt_begin(pt); /* 初始化一次 */ pt_timer_reset(&timer); for(;;) { pt_wait(pt, !(pt_queue_empty(q))); ptr = pt_queue_pop(q); printf("consume data: %d!\n", *ptr); ptr = NULL; pt_yield(pt); } pt_end(pt); } void product_task(struct pt *pt) { static uint32_t timer = 0; pt_begin(pt); /* 初始化一次 */ pt_timer_reset(&timer); for(;;) { pt_wait(pt, !(pt_queue_full(q))); if(pt_timer_expired(&timer, 8)) { data++; pt_queue_push(q, data); printf("product data: %d!\n", data); pt_timer_reset(&timer); } pt_yield(pt); } pt_end(pt); }
参考文献 纯C语言|实现协程框架,底层原理与性能分析 protothreads