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