Linux线程——互斥锁

一、互斥锁的概念及理解

互斥锁(Mutex,全称 Mutual Exclusion Lock)是 Linux 多线程编程中用于保护共享资源的一种同步机制。它确保在任意时刻,只有一个线程可以访问特定的共享资源或代码段(即临界区),从而避免数据竞争和不一致问题。互斥锁本质上是一种二进制锁,只有两种状态:锁定(Locked)和解锁(Unlocked)。

核心特性

  1. 原子性:加锁和解锁操作是原子的,操作系统保证同一时间只有一个线程能成功加锁。
  2. 唯一性:一旦线程锁定互斥锁,其他线程在锁被释放前无法再次锁定。
  3. 非忙等待:若锁已被占用,其他线程会进入睡眠状态(不占用 CPU 资源),直到锁被释放后由内核唤醒。

互斥锁的工作原理

互斥锁内部通常包含:

  • 锁状态:锁定/未锁定

  • 等待队列:存储等待获取锁的线程

  • 所有者:当前持有锁的线程ID

类比理解
以卫生间为例,当一个人进入卫生间并锁门(加锁)后,其他人无法进入(阻塞);当此人离开并解锁后,其他人可尝试进入,但一次只能进入一人。

二、互斥锁的使用方法

典型流程

  1. 初始化互斥锁:动态或静态初始化。
  2. 加锁:线程访问共享资源前调用 pthread_mutex_lock
  3. 访问共享资源:执行临界区代码。
  4. 解锁:线程完成操作后调用 pthread_mutex_unlock
  5. 销毁互斥锁:不再使用时调用 pthread_mutex_destroy(动态初始化的锁需手动销毁)。

代码示例

#include <pthread.h>
#include <stdio.h>

pthread_mutex_t mutex; // 定义互斥锁
int shared_resource = 0; // 共享资源

void* thread_func(void* arg) {
    pthread_mutex_lock(&mutex); // 加锁
    shared_resource++; // 访问共享资源
    printf("Thread %ld: shared_resource = %d\n", (long)arg, shared_resource);
    pthread_mutex_unlock(&mutex); // 解锁
    return NULL;
}

int main() {
    pthread_t t1, t2;

    // 初始化互斥锁(动态初始化)
    if (pthread_mutex_init(&mutex, NULL) != 0) {
        perror("pthread_mutex_init failed");
        return 1;
    }

    // 创建线程
    pthread_create(&t1, NULL, thread_func, (void*)1);
    pthread_create(&t2, NULL, thread_func, (void*)2);

    // 等待线程结束
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);

    // 销毁互斥锁
    pthread_mutex_destroy(&mutex);
    return 0;
}

输出

Thread 1: shared_resource = 1
Thread 2: shared_resource = 2

三、互斥锁的注意事项

  1. 加锁与解锁必须成对出现
    遗漏解锁会导致其他线程永久阻塞,引发死锁。

  2. 避免嵌套加锁
    非递归锁(默认类型)不允许同一线程多次加锁,否则会死锁。递归锁(PTHREAD_MUTEX_RECURSIVE_NP)支持同一线程多次加锁,但需谨慎使用。

  3. 防止死锁

    • 固定加锁顺序:多个锁时,所有线程按相同顺序请求锁。
    • 超时机制:使用 pthread_mutex_trylock 或 pthread_mutex_timedlock 避免无限等待。
    • 示例死锁场景
pthread_mutex_t mutex1, mutex2;

void* threadA(void* arg) {
    pthread_mutex_lock(&mutex1);
    sleep(1); // 模拟工作
    pthread_mutex_lock(&mutex2); // 死锁:线程B已持有mutex2并等待mutex1
    // ...
}

void* threadB(void* arg) {
    pthread_mutex_lock(&mutex2);
    sleep(1); // 模拟工作
    pthread_mutex_lock(&mutex1); // 死锁
    // ...
}
  1. 性能考虑
    过度使用互斥锁会导致线程频繁阻塞和唤醒,降低并发性能。应尽量缩小临界区范围。

  2. 错误处理
    检查所有互斥锁 API 的返回值,确保操作成功。


四、互斥锁相关 API 概要总结

API 名称功能初始化/销毁加锁/解锁非阻塞加锁超时加锁
函数pthread_mutex_init
pthread_mutex_destroy
pthread_mutex_lock
pthread_mutex_unlock
pthread_mutex_trylockpthread_mutex_timedlock
属性控制支持(通过 pthread_mutexattr_t---

五、详细 API 介绍

1. pthread_mutex_init

功能:动态初始化互斥锁。
函数原型

#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);

参数

  • mutex:指向互斥锁变量的指针。
  • attr:指向互斥锁属性的指针。若为 NULL,使用默认属性(普通非递归锁)。

返回值

  • 成功:返回 0
  • 失败:返回错误码(如 ENOMEM 内存不足,EINVAL 参数无效)。

示例

pthread_mutex_t mutex;
if (pthread_mutex_init(&mutex, NULL) != 0) {
    perror("pthread_mutex_init failed");
}

2. pthread_mutex_destroy

功能:销毁互斥锁,释放其占用的资源。
函数原型

int pthread_mutex_destroy(pthread_mutex_t *mutex);

参数

  • mutex:指向待销毁的互斥锁变量的指针。

返回值

  • 成功:返回 0
  • 失败:返回错误码(如 EBUSY 锁仍被占用,EINVAL 参数无效)。

注意事项

  • 确保锁未被任何线程持有

  • 确保没有线程在等待该锁

  • 静态初始化的互斥锁不需要销毁

示例

pthread_mutex_destroy(&mutex);

3. pthread_mutex_lock

功能:对互斥锁加锁。若锁已被占用,调用线程阻塞直到锁被释放。

行为描述

  • 如果锁可用,立即获得锁并返回

  • 如果锁被其他线程持有,则调用线程阻塞直到锁可用

  • 同一线程重复加锁可能导致死锁(普通锁)

函数原型

int pthread_mutex_lock(pthread_mutex_t *mutex);

参数

  • mutex:指向互斥锁变量的指针。

返回值

  • 成功:返回 0
  • 失败:返回错误码(如 EINVAL 锁无效,EDEADLK 检测到死锁)。

示例

pthread_mutex_lock(&mutex);
// 临界区代码
pthread_mutex_unlock(&mutex);

4. pthread_mutex_unlock

功能:释放互斥锁,允许其他线程加锁。
函数原型

int pthread_mutex_unlock(pthread_mutex_t *mutex);

参数

  • mutex:指向互斥锁变量的指针。

返回值

  • 成功:返回 0
  • 失败:返回错误码(如 EINVAL 锁无效,EPERM 线程未持有锁)。

注意事项

  • 必须由锁的持有者调用

  • 解锁后等待队列中的一个线程将被唤醒

  • 不要解锁未锁定的互斥锁

示例

pthread_mutex_unlock(&mutex);

5. pthread_mutex_trylock

功能:非阻塞尝试加锁。若锁已被占用,立即返回错误码 EBUSY 而非阻塞。

行为特点

  • 尝试获取锁,但不会阻塞

  • 成功获取返回0

  • 锁不可用时立即返回EBUSY

函数原型

int pthread_mutex_trylock(pthread_mutex_t *mutex);

参数

  • mutex:指向互斥锁变量的指针。

返回值

  • 成功:返回 0
  • 失败:返回错误码(如 EBUSY 锁被占用,EINVAL 锁无效)。

使用场景

  • 避免死锁

  • 实现轮询机制

示例

if (pthread_mutex_trylock(&mutex) == 0) {
// 加锁成功,执行临界区代码
pthread_mutex_unlock(&mutex);
} else {
// 加锁失败,处理其他逻辑
}

6. pthread_mutex_timedlock

功能:在指定时间内尝试加锁。若超时仍未获取锁,返回错误码 ETIMEDOUT
函数原型

int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);

参数

  • mutex:指向互斥锁变量的指针。
  • abstime:指向 timespec 结构的指针,指定超时时间(绝对时间)。

返回值

  • 成功:返回 0
  • 失败:返回错误码(如 ETIMEDOUT 超时,EINVAL 参数无效)。

示例

struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
ts.tv_sec += 2; // 设置超时时间为当前时间 + 2秒


if (pthread_mutex_timedlock(&mutex, &ts) == 0) {
// 加锁成功
pthread_mutex_unlock(&mutex);
} else {
// 加锁失败(超时)
}

六、互斥锁属性(pthread_mutexattr_t

通过 pthread_mutexattr_t 可自定义互斥锁行为,常见属性类型包括:

  1. PTHREAD_MUTEX_NORMAL:普通锁,非递归,检错行为依赖实现。
  2. PTHREAD_MUTEX_ERRORCHECK:检错锁,同一线程重复加锁返回错误。
  3. PTHREAD_MUTEX_RECURSIVE:递归锁,同一线程可多次加锁,需对应次数解锁。
  4. PTHREAD_MUTEX_DEFAULT:默认锁,行为依赖实现(通常为普通锁)。

设置属性示例

pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); // 设置为递归锁

pthread_mutex_t mutex;
pthread_mutex_init(&mutex, &attr);

pthread_mutexattr_destroy(&attr); // 销毁属性对象

七、使用互斥锁的注意事项

1. 避免死锁的黄金法则

  1. 固定加锁顺序:所有线程按相同顺序获取多个锁

  2. 尝试加锁:使用pthread_mutex_trylock避免无限等待

  3. 锁超时:使用pthread_mutex_timedlock设置等待上限

  4. 锁粒度:锁的粒度要尽可能小,持有时间尽可能短

2. 常见错误及预防

错误类型后果预防措施
忘记解锁死锁使用RAII模式,确保解锁
重复加锁死锁使用错误检查锁或递归锁
解锁未持有锁未定义行为检查锁的所有权
破坏锁变量未定义行为避免修改已初始化的锁
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hardStudy_h

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值