纯C无操作系统轻量协程库Protothread使用记录

发布时间:2024年01月22日

目的

在单片机开发中很多时候都是无操作系统环境,这时候如果要实现异步操作,并且流程逻辑比较复杂时处理起来会稍稍麻烦。这时候可以试试 Protothread 这个协程库。

官网: https://dunkels.com/adam/pt/

Protothreads are extremely lightweight stackless threads designed for severely memory constrained systems, such as small embedded systems or wireless sensor network nodes. Protothreads provide linear code execution for event-driven systems implemented in C. Protothreads can be used with or without an underlying operating system to provide blocking event-handlers. Protothreads provide sequential flow of control without complex state machines or full multi-threading.


Protothreads是为内存严重受限的系统(如小型嵌入式系统或无线传感器网络节点)设计的极为轻量级的无堆栈线程。协议线程为在C中实现的事件驱动系统提供线性代码执行。协议线程可以与底层操作系统一起使用,也可以不与底层操作体系一起使用,以提供阻塞事件处理程序。原线程提供顺序控制流,而无需复杂的状态机或完整的多线程。

这篇文章主要是我自己使用入门记录。具体是实现原理细节等可以参考官网的文档或是下面文章:
《一个“蝇量级” C 语言协程库》https://coolshell.cn/articles/10975.html

源码说明

从官网下载Protothread库解压后里面就包含了源码、例程和文档:
在这里插入图片描述
在这里插入图片描述

整个库总共就五个头文件:

  • pt.h 协程库用户接口;
  • lc.h 用来选择具体协程的实现方式(默认为 lc-switch.h ,可以手动在这里更改);
  • lc-switch.h 使用 C语言 switch/case 语法实现的协程(使用该方式时协程函数中不能使用 switch/case ,可能会冲突);
  • lc-addrlabels.h 使用 gcc label 特性实现的协程(这个依赖GCC编译器);
  • pt-sem.h 信号量实现;

pt.h 中几个数据接口和接口如下:

// 协程控制数据结构
struct pt {
  lc_t lc;
};

// lc-switch.h中lc_t原型为typedef unsigned short lc_t;
// lc-addrlabels.h中lc_t原型为typedef void * lc_t;

// 以下是协程调度过程中的一些返回状态
#define PT_WAITING 0
#define PT_YIELDED 1
#define PT_EXITED  2
#define PT_ENDED   3

PT_INIT(pt) // 初始化控制数据结构(设置lc=0)

PT_THREAD(name_args) // 声明一个协程的函数(这个用不用无所谓,官方的例程有时候也没用)
PT_BEGIN(pt) // 协程入口
PT_END(pt) // 协程出口

PT_WAIT_UNTIL(pt, condition) // 等待condition为真向下运行,否则跳出当前协程
PT_WAIT_WHILE(pt, cond) // 和PT_WAIT_UNTIL相反,当cond为假向下运行,否则跳出当前协程

PT_WAIT_THREAD(pt, thread) // 等待子协程thread调度完成
PT_SPAWN(pt, child, thread) // 启动子协程thread,并等待其完成。child是子协程的pt

PT_RESTART(pt) // 重置协程
PT_EXIT(pt) // 退出协程

PT_SCHEDULE(f) // 调度一个协程,如果协程还在运行则返回值非0,如果协程退出则返回值为0
PT_YIELD(pt) // 主动出让协程
PT_YIELD_UNTIL(pt, cond) // 等待cond为真向下运行,否则出让当前协程

pt-sem.h 中几个数据接口和接口如下:

// 信号量数据结构
struct pt_sem {
  unsigned int count;
};

PT_SEM_INIT(s, c) // 初始化信号量值等于c
PT_SEM_WAIT(pt, s) // 等待信号量可用(>0),向下运行并消耗一个信号量
PT_SEM_SIGNAL(pt, s) // 给出一个信号量

使用演示

下面是个最简单的演示:
在这里插入图片描述

用上信号量的话上面代码可以改写成下面这样:

#include <stdio.h>
#include "pt-sem.h"

static time_t pretime = 0, nowstamp;

// 以下为信号量
static struct pt_sem sem1;
// 以下为协程控制数据
static struct pt pt1;
// 以下为协程函数
static PT_THREAD(protothread1(struct pt *pt)) {
  PT_BEGIN(pt); // 协程入口
  printf("Protothread1 begin\n\n");
  while(1) {
    PT_SEM_WAIT(pt, &sem1); // 等待信号量可用,并消耗信号量
    time(&nowstamp);
    printf("Protothread1 running, current time is %s\n", ctime(&nowstamp));
  }
  PT_END(pt); // 协程出口
}

int main(void) {
  time(&pretime);

  PT_INIT(&pt1); // 初始化协程控制数据结构
  PT_SEM_INIT(&sem1, 0); // 初始化信号量

  while(1) {
    protothread1(&pt1); // 运行协程

    // 以下代码每2s给出一个sem
    time(&nowstamp);
    if((nowstamp - pretime) >= 2) {
        pretime = nowstamp;
        PT_SEM_SIGNAL(&pt1, &sem1); // 给出信号量
    }
  }
}

在这里插入图片描述

下面是一个协程间调用的演示:

#include <stdio.h>
#include "pt.h"

static PT_THREAD(childpt(struct pt *pt)) {
  static int counter = 4; // 使用函数内部静态变量保存状态
  PT_BEGIN(pt); // 协程入口
  printf("childpt begin\n\n");

  while(counter--) {
    printf("childpt running, counter = %d\n\n", counter);
    PT_YIELD(pt); // 主动出让CPU
    printf("childpt resume run\n\n");
  }

  printf("childpt end\n\n");
  PT_END(pt); // 协程出口
}

static PT_THREAD(parentpt(struct pt *pt)) {
  static struct pt child;

  PT_BEGIN(pt); // 协程入口
  printf("parentpt begin\n\n");

  PT_SPAWN(pt, &child, childpt(&child)); // 调度子协程直至运行结束

  printf("parentpt end\n\n");
  PT_END(pt); // 协程出口
}

int main(void) {
  static struct pt parant;

  PT_INIT(&parant); // 初始化协程控制数据结构

  while(PT_SCHEDULE(parentpt(&parant))); // 调度父协程直至运行结束
  while(1);
}

在这里插入图片描述

总结

Protothread使用起来比较简单,当然功能也比较简单。另外使用时还有一定的限制,比如使用默认实现时不能在协程中使用 switch/case ,需要在协程中使用静态变量来保存相关数据等。

如果上了操作系统的话,Protothread这种协程相对来说意义一般,但是对于没有操作系统的单片机开发这些来说Protothread就非常好用了。

文章来源:https://blog.csdn.net/Naisu_kun/article/details/135396601
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。