Protothreads库学习(一) - 阻塞等待
Protothreads库介绍
Protothreads是一个极其轻量的协程库,资源开销极小,适合于具有协程需求但单片机资源不足以运行RTOS的嵌入式系统等环境。
Protothreads库安装
在Protothreads - Lightweight, Stackless Threads in C下载源代码即可,包括三个.c示例文件与五个.h头文件,其中头文件是库本体。lc-switch.h与lc-addrlabels.h是该库的两种实现方案,需根据编译器与需求进行二选一,默认使用lc-switch.h,下面的讲解也是基于lc-switch.h进行讲解。
阻塞等待应用
打开example-small.c文件,其中包含两个线程函数与一个main函数。
在main函数前,先定义了两个静态结构体变量,这是用于保存线程断点的结构体,其中仅包含一个正整数变量lc。
1 | |
在main函数中,先使用PT_INIT宏对pt结构体变量进行初始化,这个宏仅仅是把结构体中的lc变量置为零。
1 | |
然后在一个无限循环中轮询调用两个线程函数。
1 | |
我们在看第一个线程函数的实现,
1 | |
这是一个静态函数,接收pt结构体指针变量,返回整形值。返回值我们无需在意,宏会帮我们处理好。
我们发现函数被两个宏包围着,分别是PT_BEGIN与PT_END,同时应用部分的代码被一块while(1)循环包裹。
循环中调用了PT_WAIT_UNTIL宏,该宏会阻塞线程直至条件成立,并让出CPU给其他线程。此时我们看第二个线程,
1 | |
其结构与第一个线程大同小异,在应用层的代码中,先把protothread2_flag置1,然后阻塞让出CPU资源。这时代码又回到线程一,阻塞条件不再成立,代码继续执行,在控制台上打印,将protothread2_flag置0,protothread1_flag置1,然后新的循环开始,线程重新阻塞,CPU回到线程二,阻塞结束,打印字符,protothread1_flag置0,开始新的循环。于是最终运行结果就是控制台上两句话交替输出。
与PT_WAIT_UNTIL类似的还有PT_WAIT_WHILE宏,其功能与PT_WAIT_UNTIL相反。PT_WAIT_UNTIL是直到条件成立才结束阻塞,而PT_WAIT_WHILE是条件成立时一直阻塞,条件不满足时才结束阻塞。
还有一点值得注意的是,Protothreads并不会保存一个线程阻塞时的运行状态,也就是说,在线程阻塞前定义的局部变量将全部是不可信的,这是该库所采用的实现方案所不可避免的。解决方案唯有使用全局变量或静态变量才可行。
除此之外,也不能在PT_BEGIN与PT_END之间使用switch语句,因为该库实现利用了switch,嵌套switch会导致问题。
阻塞等待原理
要清楚Protothreads的实现原理,需要了解一个特殊的C语言特性,详情见下面的代码。
1 | |
在其他编程语言中,这种情况是不合语法的,但在C语言中,这种情况是合理的。
我们很容易知道n为0时,代码会正常输出i,j的值,但当n为1时,代码会如何运行呢?
答案是代码会直接跳转到case 1:后面运行,此时j就成为了未定义的变量,输出值不可预期(UB)。
上面的代码就是Protothreads的原理,我们调用的宏PT_BEGIN与PT_END就是创建了一个与上面代码类似的switch-case代码块,当调用阻塞时就是创建了一个类似上面case 1:的语句,在pt中写入相应的变量,此后switch就从此处开始执行,这也是应用部分提及到的局部变量失效的原因。