3. 线程同步之互斥锁
任务目标
在Zephyr RTOS中创建两个线程,它们同时对一个共享计数器执行“读取-修改-写入”操作。由于该操作非原子性,若不加以保护会产生竞态条件,导致最终计数值小于预期。本案例使用互斥锁(Mutex)保护该临界区,确保每次只有一个线程能修改共享资源,最终计数值严格等于预期值。
核心知识点
- 互斥锁的初始化与获取/释放API:
k_mutex_init()、k_mutex_lock()、k_mutex_unlock() - 临界区(Critical Section)的概念:需要互斥访问的共享资源代码段
- 线程优先级与调度:两个线程以相同优先级运行,通过
k_sleep()主动让出CPU
完整代码
#include <zephyr/kernel.h>
/* 共享资源 */
static int shared_counter = 0;
/* 互斥锁 */
static struct k_mutex counter_mutex;
/* 每个线程执行的操作次数 */
#define INCREMENT_COUNT 1000
/* 线程入口函数:对共享计数器执行非原子自增 */
static void counter_thread(void *arg1, void *arg2, void *arg3)
{
ARG_UNUSED(arg1);
ARG_UNUSED(arg2);
ARG_UNUSED(arg3);
for (int i = 0; i < INCREMENT_COUNT; i++) {
/* 获取互斥锁,进入临界区 */
k_mutex_lock(&counter_mutex, K_FOREVER);
/* 临界区:读取-修改-写入 */
int temp = shared_counter;
temp = temp + 1;
shared_counter = temp;
/* 释放互斥锁,离开临界区 */
k_mutex_unlock(&counter_mutex);
/* 让出CPU,给另一个线程运行机会 */
k_sleep(K_USEC(10));
}
}
/* 定义两个线程栈与线程对象 */
#define THREAD_STACK_SIZE 512
K_THREAD_STACK_DEFINE(thread_a_stack, THREAD_STACK_SIZE);
K_THREAD_STACK_DEFINE(thread_b_stack, THREAD_STACK_SIZE);
static struct k_thread thread_a_data;
static struct k_thread thread_b_data;
int main(void)
{
/* 初始化互斥锁 */
k_mutex_init(&counter_mutex);
printk("启动两个线程,每个线程执行 %d 次自增操作\n", INCREMENT_COUNT);
/* 创建两个相同优先级的线程 */
k_thread_create(&thread_a_data, thread_a_stack, THREAD_STACK_SIZE,
counter_thread, NULL, NULL, NULL,
5, 0, K_NO_WAIT);
k_thread_create(&thread_b_data, thread_b_stack, THREAD_STACK_SIZE,
counter_thread, NULL, NULL, NULL,
5, 0, K_NO_WAIT);
/* 等待两个线程执行完毕 */
k_thread_join(&thread_a_data, K_FOREVER);
k_thread_join(&thread_b_data, K_FOREVER);
/* 打印最终结果 */
printk("两个线程执行完毕,共享计数器最终值: %d\n", shared_counter);
printk("预期值: %d\n", INCREMENT_COUNT * 2);
if (shared_counter == INCREMENT_COUNT * 2) {
printk("验证通过:互斥锁正确保护了共享资源\n");
} else {
printk("验证失败:存在竞态条件\n");
}
return 0;
}
工程配置
在prj.conf中添加以下配置(如已有则无需重复):
CONFIG_PRINTK=y
构建与运行
- 将上述代码保存为项目中的
src/main.c,并确保CMakeLists.txt正确指向该源文件。 - 在Zephyr工程目录下执行构建命令(以
qemu_x86目标为例):
west build -b qemu_x86 -t run
验证方法
运行程序后,观察串口输出。判定通过的唯一标准是最后一行输出为:
验证通过:互斥锁正确保护了共享资源
同时,共享计数器最终值必须等于2000。若输出“验证失败”或最终值小于2000,说明互斥锁未正确生效(例如忘记加锁或解锁路径不完整)。
常见问题
- 忘记初始化互斥锁:调用
k_mutex_init()是必须的,否则锁行为未定义。 - 死锁:本案例中锁内无阻塞调用,不会死锁。若在临界区内调用会阻塞的API(如
k_sleep()),可能导致死锁或优先级反转。 - 验证竞态条件:若将
k_mutex_lock()/k_mutex_unlock()注释掉重新编译运行,最终值通常会小于2000,可直观对比加锁与不加锁的差异。