4. 线程同步之信号量

任务目标

在 Zephyr RTOS 中,使用信号量(Semaphore)实现两个任务之间的同步,并演示信号量作为资源计数器的基本用法。本案例将创建两个线程:一个生产者线程周期性释放信号量,一个消费者线程获取信号量后执行处理,以此展示线程间的同步与资源计数机制。

环境依赖

本案例基于 Zephyr RTOS 3.x,使用 native_sim 或任意支持的内核模拟目标。需要确保 CONFIG_SEMAPHORE 已启用(Zephyr 默认启用)。无需额外硬件依赖。

完整实现

#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>

/* 定义信号量:最大计数为 3,初始计数为 0 */
K_SEM_DEFINE(my_sem, 0, 3);

/* 生产者线程:每 500ms 释放一个信号量 */
void producer_thread(void *arg1, void *arg2, void *arg3)
{
    ARG_UNUSED(arg1);
    ARG_UNUSED(arg2);
    ARG_UNUSED(arg3);

    while (1) {
        k_sem_give(&my_sem);
        printk("Producer: semaphore given (count=%u)\n",
               k_sem_count_get(&my_sem));
        k_sleep(K_MSEC(500));
    }
}

/* 消费者线程:获取信号量,若成功则处理 */
void consumer_thread(void *arg1, void *arg2, void *arg3)
{
    ARG_UNUSED(arg1);
    ARG_UNUSED(arg2);
    ARG_UNUSED(arg3);

    while (1) {
        k_sem_take(&my_sem, K_FOREVER);
        printk("Consumer: semaphore taken (count=%u)\n",
               k_sem_count_get(&my_sem));
        /* 模拟处理耗时 */
        k_sleep(K_MSEC(200));
    }
}

/* 定义并初始化两个线程 */
K_THREAD_DEFINE(producer_tid, 1024,
                producer_thread, NULL, NULL, NULL,
                5, 0, 0);
K_THREAD_DEFINE(consumer_tid, 1024,
                consumer_thread, NULL, NULL, NULL,
                4, 0, 0);

构建与运行

  1. 将上述代码保存为 main.c,放入一个 Zephyr 应用工程的 src 目录。
  2. 在应用目录下创建 prj.conf(本案例无需额外配置项,文件可为空或仅包含注释)。
  3. 使用以下命令构建并运行(以 native_sim 为例):
west build -b native_sim .
west build -t run

验证方法

程序启动后,观察串口输出。预期输出模式如下(具体计数可能因调度时序略有差异):

Producer: semaphore given (count=1)
Consumer: semaphore taken (count=0)
Producer: semaphore given (count=1)
Consumer: semaphore taken (count=0)
...

通过条件:

  • 生产者与消费者交替打印,且消费者每次获取信号量后计数减 1,生产者释放后计数加 1。
  • 信号量计数始终不超过 3(最大限制)。若生产者连续释放超过 3 次而消费者未及时获取,计数将饱和在 3,后续 k_sem_give 不会增加计数——可观察计数稳定在 3 来验证资源上限。
  • 消费者使用 K_FOREVER 阻塞等待,不会出现空转忙等。

若需验证资源计数上限,可将消费者线程优先级调低或删除消费者线程,观察生产者连续释放时计数封顶在 3。

关键说明

  • K_SEM_DEFINE 的第三个参数为信号量最大计数,用于限制资源数量;第二个参数为初始可用计数。
  • 生产者优先级(5)高于消费者(4),数值越小优先级越高,因此生产者会先运行并释放信号量。
  • 本案例仅演示信号量的同步与计数功能,不涉及互斥访问共享资源;如需互斥保护,应使用互斥量(Mutex)。