1. Home
  2. 博客

    nRF Connect SDK 基础课程第八课:线程同步

nRF Connect SDK 基础课程第八课:线程同步

Nordic Semiconductor

在多线程应用中,多个线程并发访问共享资源会引发竞态条件,导致数据异常与程序崩溃。nRF Connect SDK 基础课程第八课线程同步系统讲解线程同步的必要性、信号量(Semaphore)与互斥锁(Mutex)的核心特性,并通过两个经典实操实验,完整演示如何解决资源计数与临界区保护问题,是构建稳定可靠多线程系统的必备核心内容。

课程定位

本课是多线程开发的安全基石。在前序课程掌握线程创建、调度、优先级与工作队列后,本课聚焦并发安全,解决多线程访问共享资源的核心痛点。通过学习信号量与互斥锁,你将能够设计无冲突、无死锁、数据可靠的并发系统,是工业级嵌入式固件开发的必备技能。

核心学习目标

  • 理解线程同步的必要性,认识临界区与竞态条件
  • 掌握信号量的特性、API 与资源计数应用场景
  • 掌握互斥锁的特性、API 与临界区保护应用场景
  • 区分信号量与互斥锁的适用场景,正确选择同步机制
  • 通过两个实操实验,独立实现资源管理与临界区保护
  • 理解所有权、优先级继承等关键概念

一、线程同步的必要性

多线程环境下,临界区(Critical Section) 是多个线程可能同时访问的共享资源(变量、结构体、硬件接口、代码段)。若不加保护地并发访问,会出现:

  • 数据读写被打断
  • 计算结果错误
  • 程序行为异常
  • 系统崩溃

线程同步确保同一时间只有一个线程执行临界区,保证操作原子性与数据一致性。Zephyr RTOS 提供两种核心同步机制:

  1. 信号量(Semaphore):资源计数与同步信号
  2. 互斥锁(Mutex):独占式临界区保护

二、信号量(Semaphore)—— 资源计数与同步信号

信号量是带计数的同步机制,用于管理有限数量的共享资源

核心特性

  • 初始化时设定初始值最大值
  • k_sem_give():释放资源,计数 + 1(可在线程 / ISR 调用)
  • k_sem_take():申请资源,计数 - 1,计数为 0 时阻塞(仅在线程调用)
  • 无所有权:任意线程可 give,任意线程可 take
  • 无优先级继承
  • 适用于:资源计数、生产者消费者、同步信号

核心 API

#include <zephyr/kernel.h>



// 静态定义信号量:名称,初始计数,最大值

K_SEM_DEFINE(instance_monitor_sem, 10, 10);



// 申请资源:等待时间K_FOREVER永久阻塞

k_sem_take(&instance_monitor_sem, K_FOREVER);



// 释放资源

k_sem_give(&instance_monitor_sem);



// 获取当前计数

k_sem_count_get(&instance_monitor_sem);

三、互斥锁(Mutex)—— 独占式临界区保护

互斥锁是二值锁,用于保护必须独占访问的临界区,同一时间只允许一个线程持有。

核心特性

  • 状态:locked/unlocked
  • 严格所有权:只有加锁线程能解锁
  • 可递归锁:同一线程可多次加锁,需对应次数解锁
  • 仅在线程使用,不可在 ISR 使用
  • 支持优先级继承,避免优先级反转
  • 适用于:共享变量、共享代码段、硬件独占访问

核心 API

#include <zephyr/kernel.h>



// 静态定义互斥锁

K_MUTEX_DEFINE(test_mutex);



// 加锁:永久等待

k_mutex_lock(&test_mutex, K_FOREVER);



// 解锁

k_mutex_unlock(&test_mutex);

四、信号量 vs 互斥锁 对比表

表格

特性

信号量

互斥锁

功能

资源计数、同步信号

独占临界区保护

取值

0~ 最大值

锁定 / 解锁

所有权

调用者

give 可 ISR,take 仅线程

仅线程

优先级继承

典型场景

资源池、生产者消费者

共享变量、设备访问

实操练习 1:信号量实现资源计数管理

目标:使用信号量限制资源最大数量,解决生产者消费者模型中的资源越界问题。

步骤 1:工程准备

打开基础工程 l8/l8_e1

步骤 2:定义线程优先级

#define PRODUCER_PRIORITY 5

#define CONSUMER_PRIORITY 5

步骤 3:定义共享资源(未加保护前会异常)

volatile uint32_t available_instance_count = 10;

步骤 4:生产者与消费者线程

void producer(void)

{

printk("Producer thread started\n");

while (1) {

release_access();

k_msleep(sys_rand32_get() % 10);

}

}



void consumer(void)

{

printk("Consumer thread started\n");

while (1) {

get_access();

k_msleep(sys_rand32_get() % 10);

}

}

步骤 5:资源申请与释放(无保护)

void get_access(void)

{

available_instance_count--;

printk("Resource taken: %d\n", available_instance_count);

}



void release_access(void)

{

available_instance_count++;

printk("Resource given: %d\n", available_instance_count);

}

现象:资源计数超过 10,数据异常。

步骤 6:加入信号量保护

// 定义信号量:初始10,最大10

K_SEM_DEFINE(instance_monitor_sem, 10, 10);

步骤 7:使用信号量保护资源操作

void get_access(void)

{

k_sem_take(&instance_monitor_sem, K_FOREVER);

printk("Resource taken: %d\n", k_sem_count_get(&instance_monitor_sem));

}



void release_access(void)

{

k_sem_give(&instance_monitor_sem);

printk("Resource given: %d\n", k_sem_count_get(&instance_monitor_sem));

}

步骤 8:注释掉原始变量

// volatile uint32_t available_instance_count = 10;

步骤 9:编译烧录

结果:资源计数严格保持在 0~10,生产者消费者稳定运行,无越界。

实操练习 2:互斥锁保护临界区,消除竞态

目标:使用互斥锁保护共享变量与代码段,确保 increment_count + decrement_count = COMBINED_TOTAL 恒成立。

步骤 1:工程准备

打开基础工程 l8/l8_e2

步骤 2:启用多线程

CONFIG_MULTITHREADING=y

步骤 3:定义线程优先级

#define THREAD0_PRIORITY 4

#define THREAD1_PRIORITY 4

步骤 4:定义共享变量

#define COMBINED_TOTAL 40

int32_t increment_count = 0;

int32_t decrement_count = COMBINED_TOTAL;

步骤 5:临界区代码(未保护会出现竞态)

void shared_code_section(void)

{

increment_count += 1;

increment_count = increment_count % COMBINED_TOTAL;



decrement_count -= 1;

if (decrement_count == 0) {

decrement_count = COMBINED_TOTAL;

}

}

步骤 6:竞态检测

uint8_t race_condition = 0;

int32_t inc_copy, dec_copy;



if (increment_count + decrement_count != COMBINED_TOTAL) {

race_condition = 1;

inc_copy = increment_count;

dec_copy = decrement_count;

}



if (race_condition) {

printk("Race condition!\n");

printk("%d + %d = %d\n", inc_copy, dec_copy, inc_copy+dec_copy);

k_msleep(400 + sys_rand32_get() % 10);

}

步骤 7:单线程运行正常,双线程立即出现竞态

现象:和不为 40,数据错误。

步骤 8:加入互斥锁保护

K_MUTEX_DEFINE(test_mutex);

步骤 9:临界区加锁 / 解锁

void shared_code_section(void)

{

// 加锁

k_mutex_lock(&test_mutex, K_FOREVER);



increment_count += 1;

increment_count = increment_count % COMBINED_TOTAL;



decrement_count -= 1;

if (decrement_count == 0) {

decrement_count = COMBINED_TOTAL;

}



// 检测并拷贝

if (increment_count + decrement_count != COMBINED_TOTAL) {

race_condition = 1;

inc_copy = increment_count;

dec_copy = decrement_count;

}



// 解锁

k_mutex_unlock(&test_mutex);



// 解锁后打印

if (race_condition) {

printk("Race condition!\n");

printk("%d + %d = %d\n", inc_copy, dec_copy, inc_copy+dec_copy);

k_msleep(400 + sys_rand32_get() % 10);

}

}

步骤 10:编译烧录

结果:无论多少线程并发,和永远等于 40,竞态完全消除

课后测评

测评范围:

  • 线程同步的必要性
  • 信号量特性、API、适用场景
  • 互斥锁特性、API、适用场景
  • 所有权与优先级继承
  • 竞态条件与临界区保护
  • 信号量与互斥锁的选择

本课学习价值

本课是多线程系统稳定运行的最后一块拼图

  • 学会识别临界区与竞态条件
  • 能用信号量实现资源计数、生产者消费者模型
  • 能用互斥锁实现独占临界区保护
  • 正确区分信号量与互斥锁,按需选择
  • 构建无数据冲突、无异常崩溃的高可靠性嵌入式系统

掌握本课内容,你就具备了开发工业级多线程并发系统的核心能力。

 

短距离

适用于短距离物联网的蓝牙低功耗及多协议系统级芯片(支持Thread、Matter、Zigbee协议)

长距离

适用于LTE-M/NB-IoT、GNSS、DECT NR+和NTN的蜂窝物联网系统级封装

Wi-Fi

低功耗Wi-Fi 6协同ICs,支持2.4 GHz/5 GHz频段选项及WPA3加密协议

电源管理ICs

适用于电池供电设备的电源管理IC(PMIC),nPM系列提供充电与稳压功能选项。

AI及软件工具

工具与NPU加速边缘人工智能开发和部署

订阅Nordic新闻简报

了解最新信息!订阅后即可获取最新Nordic及物联网资讯

立即订阅