3. 线程同步之互斥锁
任务目标
创建两个线程,分别对同一个全局计数器执行多次递增操作。由于递增操作并非原子操作,多线程并发访问会导致数据丢失。使用Zephyr的互斥锁(mutex)保护临界区,确保计数器最终值等于两个线程递增次数之和。
环境依赖
本案例基于Zephyr RTOS,需要以下配置:
- 开发板:任意支持Zephyr的开发板(如qemu_x86、nrf52840dk_nrf52840)
- 在
prj.conf中启用互斥锁支持:CONFIG_MUTEX=y
关键完整代码
#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>
/* 共享资源:全局计数器 */
static int shared_counter;
/* 互斥锁定义 */
static struct k_mutex counter_mutex;
/* 线程A:递增1000次 */
void thread_a(void *arg1, void *arg2, void *arg3)
{
for (int i = 0; i < 1000; i++) {
k_mutex_lock(&counter_mutex, K_FOREVER);
shared_counter++; /* 临界区 */
k_mutex_unlock(&counter_mutex);
}
}
/* 线程B:递增1000次 */
void thread_b(void *arg1, void *arg2, void *arg3)
{
for (int i = 0; i < 1000; i++) {
k_mutex_lock(&counter_mutex, K_FOREVER);
shared_counter++; /* 临界区 */
k_mutex_unlock(&counter_mutex);
}
}
/* 线程栈与线程定义 */
K_THREAD_STACK_DEFINE(stack_a, 1024);
K_THREAD_STACK_DEFINE(stack_b, 1024);
struct k_thread thread_a_data;
struct k_thread thread_b_data;
void main(void)
{
/* 初始化互斥锁 */
k_mutex_init(&counter_mutex);
/* 创建两个线程,优先级相同 */
k_thread_create(&thread_a_data, stack_a, K_THREAD_STACK_SIZEOF(stack_a),
thread_a, NULL, NULL, NULL, 5, 0, K_NO_WAIT);
k_thread_create(&thread_b_data, stack_b, K_THREAD_STACK_SIZEOF(stack_b),
thread_b, 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("Final counter value: %d\n", shared_counter);
printk("Expected value: 2000\n");
}
运行与验证
- 将上述代码保存为项目主文件(如
src/main.c),并确保prj.conf中包含CONFIG_MUTEX=y。 - 在Zephyr项目目录下执行编译:
west build -b qemu_x86 . - 运行:
west build -t run
验证方法:观察串口输出。若程序正确执行,最终打印的Final counter value应为2000,与Expected value一致。若去掉互斥锁(注释掉k_mutex_lock/k_mutex_unlock),由于竞争条件,最终值通常小于2000,以此对比验证互斥锁的保护效果。
关键说明
- 互斥锁的初始化必须在任何线程尝试加锁之前完成,本案例在
main()中调用k_mutex_init()。 K_FOREVER表示线程会一直等待直到获得锁,适用于本案例中锁竞争时间极短的场景。- 两个线程优先级相同(均为5),Zephyr的时间片轮转调度会使它们交替执行,从而暴露无锁时的竞争问题。
- 本案例仅演示互斥锁的基本用法,实际工程中若临界区极短,可考虑使用原子操作或自旋锁以获得更好性能。