国际访客建议访问 Primers 编程伙伴 国际版站点 > C 教程 > mtx_init 以获得更好的体验。

# C 语言标准库函数 mtx_init

在头文件 threads.h 中定义。
/*********************************************
 * @brief 创建互斥量
 * @param[out] mutex 返回互斥量 ID
 * @param type 互斥量类型
 * @return 是否成功
 ********************************************/
int mtx_init(mtx_t* mutex, int type);

!subtitle:说明

创建一个互斥量,通过参数 mutex 返回互斥量 ID。

不再使用时需要通过 mtx_destroy 函数清理互斥量。

类型 type 可以是以下值:

  • mtx_plain - 创建一个简单的互斥量

  • mtx_timed - 创建一个支持超时的互斥量

  • mtx_plain | mtx_recursive - 创建一个普通的可递归互斥量

  • mtx_timed | mtx_recursive - 创建一个支持超时的可递归互斥量

!subtitle:参数

  • mutex - 返回互斥量 ID

  • type - 互斥量类型

!subtitle:返回值

  • 成功时返回 thrd_success

  • 失败时返回 thrd_error

# 示例

#include <stdio.h>
#include <threads.h>

// 共享数据和互斥量
struct Args
{
    int n;
    mtx_t mutex;
};

// 线程函数1,不加锁,结果错误
int func1(void* data)
{
    struct Args* args = (struct Args*)data;

    for (int i = 0; i < 10; i++)
    {
        int value = args->n + 1;
        for (int i = 0; i < 30000; i += 1); // 延长执行时间,提高不加锁时的出错概率
        args->n = value;
    }
    
    return 0;
}

// 线程函数2,加锁,结果正确
int func2(void* data)
{
    struct Args* args = (struct Args*)data;

    for (int i = 0; i < 10; i++)
    {
        mtx_lock(&args->mutex); // 锁定 mutex
        int value = args->n + 1;
        for (int i = 0; i < 30000; i += 1); // 延长执行时间,提高不加锁时的出错概率
        args->n = value;
        mtx_unlock(&args->mutex); // 解锁 mutex
    }
    
    return 0;
}

int main(void)
{
    struct Args data1, data2;
    mtx_init(&data2.mutex, mtx_plain); // 创建互斥量

    data1.n = 0;
    data2.n = 0;

    // 创建线程
    thrd_t th1, th2, th3, th4;
    thrd_create(&th1, func1, &data1); // 通过参数传递数据和互斥量
    thrd_create(&th2, func1, &data1);
    thrd_create(&th3, func2, &data2);
    thrd_create(&th4, func2, &data2);

    // 等待线程结束
    thrd_join(th1, NULL);
    thrd_join(th2, NULL);
    thrd_join(th3, NULL);
    thrd_join(th4, NULL);

    printf("data1 的最终值为 %d \n", data1.n);
    printf("data2 的最终值为 %d \n", data2.n);

    // 清除互斥量
    mtx_destroy(&data2.mutex);
    
    return 0;
}

!subtitle:说明

示例中的 func1func2 函数都是循环十次将数据的值加 1,最终将数据的值加 10。

由于这两个函数各创建了两个线程,所以正确的结果应该是 20。

但是因为 func1 没有加锁,因此可能产生这样的错误步骤:

  1. data.n 的值是 \(x\),线程 1 计算 value = data.n + 1 得到值 \(x + 1\)

  2. 发生线程调度,执行线程 2,data.n 的值仍是 \(x\),线程 2 计算 value = data.n + 1 得到值 \(x + 1\)

  3. 线程 1 执行 args->n = valuedata.n 的值被修改为 \(x + 1\)

  4. 发生线程调度,执行线程 1,线程 1 执行 args->n = valuedata.n 的值被修改为 \(x + 1\)

两个线程各执行一次加 1,原本应该总共将值加 2,但是由于线程调度导致只加了 1。

这样的步骤可能随机发生在任何时候,因此产生了随机的结果。

func2 通过互斥量,在计算 value = data.n + 1 之前加锁,执行 args->n = value 后解锁;

保障了步骤的原子性,避免了上述错误的发生。

!subtitle:多次运行的可能结果

$ ./a.out
data1 的最终值为 20
data2 的最终值为 20
$ ./a.out
data1 的最终值为 14
data2 的最终值为 20
$ ./a.out
data1 的最终值为 20
data2 的最终值为 20
$ ./a.out
data1 的最终值为 14
data2 的最终值为 20
$ ./a.out
data1 的最终值为 15
data2 的最终值为 20
$ ./a.out
data1 的最终值为 13
data2 的最终值为 20
$ ./a.out
data1 的最终值为 10
data2 的最终值为 20
$ ./a.out
data1 的最终值为 10
data2 的最终值为 20
$ ./a.out
data1 的最终值为 11
data2 的最终值为 20

# 互斥量类型

互斥量类型 说明
mtx_plain 简单的互斥量,只能被加锁一次,不支持超时
mtx_timed 支持超时的互斥量,只能被加锁一次,支持超时
mtx_plain \| mtx_recursive 普通的可递归互斥量,可以被同一个线程加锁多次,需要解锁相同的次数,不支持超时
mtx_timed \| mtx_recursive 支持超时的可递归互斥量,可以被同一个线程加锁多次,需要解锁相同的次数,支持超时

# 推荐阅读

# 参考标准

  • C17 standard (ISO/IEC 9899:2018):

    • 7.26.4.2 The mtx_init function (p: 277-278)

  • C11 standard (ISO/IEC 9899:2011):

    • 7.26.4.2 The mtx_init function (p: 381)

本文 更新于: 2025-11-27 09:38:09 创建于: 2025-11-27 09:38:09