目标:掌握 OpenHarmony 轻量系统的互斥锁 API,实现多线程共享资源的互斥访问
前置条件:已完成 Day 6 的事件标志组教程


一、工程结构

app/
├── BUILD.gn
└── 05_mutex/                   # 模块目录
    ├── BUILD.gn
    └── demo.c                  # 互斥锁测试代码

1.1 app/BUILD.gn

import("//build/lite/config/component/lite_component.gni")

lite_component("app") {
  features = [
    "05_mutex:mutex_demo",       # 引用 05_mutex 模块
  ]
}

1.2 05_mutex/BUILD.gn

static_library("mutex_demo") {
    sources = [
        "demo.c"
    ]

    include_dirs = [
        "//utils/native/lite/include",
        "//kernel/liteos_m/components/cmsis/2.0",
    ]
}

二、完整代码详解

2.1 头文件与宏定义

#include <stdio.h>      // 标准输入输出
#include <unistd.h>     // UNIX 标准函数
#include "ohos_init.h"  // OpenHarmony 系统初始化
#include "cmsis_os2.h"  // CMSIS-RTOS2 接口

#define STACK_SIZE      (1024)   // 线程栈大小
#define DELAY_TICKS_5   (5)      // 5 tick 延时
#define DELAY_TICKS_13  (13)     // 13 tick 延时
#define DELAY_TICKS_17  (17)     // 17 tick 延时
#define NUMBER_2        (2)      // 奇偶判断除数
#define NUMBER_100      (100)    // 计数上限

static int g_test_value = 0;      // 全局共享变量(临界资源)

关键g_test_value全局共享变量,三个线程同时访问,必须用互斥锁保护,否则会出现竞态条件(Race Condition)。

2.2 工作线程(共享资源访问)

void number_thread(osMutexId_t *arg)
{
    osMutexId_t *mid = arg;       // 接收传入的互斥锁 ID 指针
    
    while (g_test_value < NUMBER_100) {
        // 1. 获取互斥锁,超时时间 100 tick
        if (osMutexAcquire(*mid, NUMBER_100) == osOK) {
            
            // 2. 进入临界区,访问共享资源
            g_test_value++;
            
            if (g_test_value % NUMBER_2 == 0) {
                printf("[Mutex Test]%s gets an even value %d.\r\n", 
                       osThreadGetName(osThreadGetId()), g_test_value);
            } else {
                printf("[Mutex Test]%s gets an odd value %d.\r\n", 
                       osThreadGetName(osThreadGetId()), g_test_value);
            }
            
            // 3. 释放互斥锁,退出临界区
            osMutexRelease(*mid);
            
            // 4. 主动延时,让出 CPU,给其他线程竞争锁的机会
            osDelay(DELAY_TICKS_5);
        }
    }
}

临界区保护流程

[获取锁] → [访问 g_test_value] → [释放锁] → [延时让出 CPU]
   ↑________________________________________________↓
                    循环直到 g_test_value >= 100

2.3 线程创建辅助函数

osThreadId_t newThread(char *name, osThreadFunc_t func, osMutexId_t *arg)
{
    osThreadAttr_t attr = {
        name,              // 线程名称
        0,                 // 属性位
        NULL,              // 控制块内存
        0,                 // 控制块大小
        NULL,              // 栈内存
        STACK_SIZE * 2,    // 栈大小 2048 字节
        osPriorityNormal,  // 普通优先级
        0,                 // 保留
        0                  // 保留
    };
    
    osThreadId_t tid = osThreadNew(func, (void *)arg, &attr);
    if (tid == NULL) {
        printf("[Mutex Test]osThreadNew(%s) failed.\r\n", name);
    } else {
        printf("[Mutex Test]osThreadNew(%s) success, thread id: %d.\r\n", name, tid);
    }
    return tid;
}

2.4 主控制线程

void rtosv2_mutex_main(void)
{
    // 1. 创建互斥锁
    osMutexAttr_t attr = {0};    // 默认属性
    osMutexId_t mid = osMutexNew(&attr);
    
    if (mid == NULL) {
        printf("[Mutex Test]osMutexNew, create mutex failed.\r\n");
    } else {
        printf("[Mutex Test]osMutexNew, create mutex success.\r\n");
    }

    // 2. 创建三个工作线程,共享同一个互斥锁
    osThreadId_t tid1 = newThread("Thread_1", number_thread, &mid);
    osThreadId_t tid2 = newThread("Thread_2", number_thread, &mid);
    osThreadId_t tid3 = newThread("Thread_3", number_thread, &mid);

    // 3. 延时 13 tick,让线程运行一段时间
    osDelay(DELAY_TICKS_13);
    
    // 4. 查询当前持有互斥锁的线程
    osThreadId_t tid = osMutexGetOwner(mid);
    printf("[Mutex Test]osMutexGetOwner, thread id: %p, thread name: %s.\r\n", 
           tid, osThreadGetName(tid));
    
    // 5. 再延时 17 tick
    osDelay(DELAY_TICKS_17);

    // 6. 清理资源:终止线程 → 删除互斥锁
    osThreadTerminate(tid1);
    osThreadTerminate(tid2);
    osThreadTerminate(tid3);
    osMutexDelete(mid);
}

2.5 系统入口

static void MutexTestTask(void)
{
    osThreadAttr_t attr = {
        .name = "rtosv2_mutex_main",
        .attr_bits = 0U,
        .cb_mem = NULL,
        .cb_size = 0U,
        .stack_mem = NULL,
        .stack_size = STACK_SIZE,
        .priority = osPriorityNormal,
    };

    if (osThreadNew((osThreadFunc_t)rtosv2_mutex_main, NULL, &attr) == NULL) {
        printf("[MutexTestTask] Failed to create rtosv2_mutex_main!\n");
    }
}

APP_FEATURE_INIT(MutexTestTask);

三、CMSIS-RTOS2 互斥锁 API 详解

3.1 osMutexNew — 创建互斥锁

osMutexId_t osMutexNew(const osMutexAttr_t *attr);
参数 说明
attr 互斥锁属性,NULL 或 {0} 为默认

默认属性特点

  • 支持递归锁定(同一线程可多次获取)
  • 支持优先级继承(防止优先级反转)

3.2 osMutexAcquire — 获取互斥锁

osStatus_t osMutexAcquire(osMutexId_t mutex_id, uint32_t timeout);
参数 说明
mutex_id 互斥锁 ID
timeout 超时时间(tick),osWaitForever = 永久等待

返回值

状态 说明
osOK (0) 获取成功
osErrorTimeout (-2) 超时
osErrorResource (-3) 资源不可用
osErrorParameter (-4) 参数错误

行为

  • 锁空闲:立即获取,线程成为所有者
  • 锁被占:线程进入 Blocked 状态,等待锁释放
  • 同一线程递归获取:计数器 +1,不会死锁

3.3 osMutexRelease — 释放互斥锁

osStatus_t osMutexRelease(osMutexId_t mutex_id);

规则

  • 只有所有者线程才能释放
  • 递归获取需对应次数释放
  • 释放后,唤醒等待队列中优先级最高的线程

3.4 osMutexGetOwner — 获取当前所有者

osThreadId_t osMutexGetOwner(osMutexId_t mutex_id);

用途:调试、监控、死锁检测。

3.5 osMutexDelete — 删除互斥锁

osStatus_t osMutexDelete(osMutexId_t mutex_id);

注意:删除前确保没有线程持有该锁。


四、为什么需要互斥锁?—— 竞态条件演示

假设没有互斥锁,三个线程同时执行 g_test_value++

初始: g_test_value = 0

Thread_1: 读取 g_test_value = 0  →  计算 0+1 = 1  → [被抢占]
Thread_2: 读取 g_test_value = 0  →  计算 0+1 = 1  →  写入 g_test_value = 1
Thread_1: [恢复] 写入 g_test_value = 1   ← 覆盖了 Thread_2 的结果!

结果: g_test_value = 1,但期望 = 2(两次++)

这种错误称为"竞态条件",在多核或抢占式调度中必然发生。

使用互斥锁后

Thread_1: [Acquire] → 读取 0 → 计算 1 → 写入 1 → [Release]
Thread_2:         等待锁...  [Acquire] → 读取 1 → 计算 2 → 写入 2 → [Release]
Thread_3:                           等待锁...  [Acquire] → 读取 2 → ...

结果: g_test_value 严格递增,无丢失

五、执行流程时序图

时间轴 →
main:    [Create Mutex]──[Create T1,T2,T3]──[Delay 13]──[GetOwner]──[Delay 17]──[Terminate All]──[Delete Mutex]
                ↓              ↓    ↓    ↓
T1:             [Acq]──[++=1]──[Rel]──[Dly5]──[Acq]──[++=2]──[Rel]──...──[++=100]──[退出]
                ↑              ↓         ↑              ↓
T2:             等待锁...──[Acq]──[++=...]──[Rel]──...──[++=100]──[退出]
                ↑                        ↓         ↑
T3:             等待锁...──────────────[Acq]──[++=...]──[Rel]──...──[++=100]──[退出]

关键: 任意时刻只有一个线程在 [Acq...Rel] 临界区内

六、底层实现:LiteOS 原生互斥锁

CMSIS-RTOS2 的 osMutexXxx 在 LiteOS-M 中的映射:

CMSIS-RTOS2 LiteOS-M 原生 说明
osMutexNew LOS_MuxCreate 创建互斥锁
osMutexAcquire LOS_MuxPend 获取互斥锁
osMutexRelease LOS_MuxPost 释放互斥锁
osMutexGetOwner LOS_MuxGetOwner 获取所有者
osMutexDelete LOS_MuxDelete 删除互斥锁

LiteOS-M 互斥锁特性:

  • 优先级继承:低优先级线程持有锁时,临时提升其优先级,防止高优先级线程饥饿
  • 递归锁定:同一线程可多次获取,需对应次数释放
  • 死锁检测:部分版本支持

七、编译与验证

7.1 编译烧录

VSCode 点击 BuildUpload,串口波特率 115200

7.2 预期输出

[Mutex Test]osMutexNew, create mutex success.
[Mutex Test]osThreadNew(Thread_1) success, thread id: 3.
[Mutex Test]osThreadNew(Thread_2) success, thread id: 4.
[Mutex Test]osThreadNew(Thread_3) success, thread id: 5.
[Mutex Test]Thread_1 gets an odd value 1.
[Mutex Test]Thread_2 gets an even value 2.
[Mutex Test]Thread_3 gets an odd value 3.
[Mutex Test]Thread_1 gets an even value 4.
...
[Mutex Test]osMutexGetOwner, thread id: 0x20001xxx, thread name: Thread_2.
...
[Mutex Test]Thread_3 gets an even value 100.

观察输出:三个线程交替获取奇偶值,无重复、无跳过,证明互斥锁工作正常。


八、总结

要点 内容
临界资源 全局变量 g_test_value,多线程共享
保护手段 osMutexAcquire → 访问 → osMutexRelease
超时机制 osMutexAcquire(mid, timeout),避免永久阻塞
所有者查询 osMutexGetOwner 用于调试监控
清理顺序 先终止线程,再删除互斥锁
底层映射 LiteOS-M LOS_MuxXxx,支持优先级继承

九、下一步

Day 8 预告:信号量(Semaphore) —— 资源计数与任务同步。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐