Linux——互斥锁

一、互斥锁(Mutex)的核心概念

1. 为什么需要互斥锁?

线程共享进程的所有资源(如全局变量、文件描述符),当多个线程同时操作共享资源 时,会出现 "竞态条件"(Race Condition)------ 数据被交替修改,导致结果错乱。

举个例子 :两个线程同时对全局变量 countcount++(看似简单的操作,实际是 "读→改→写" 三步):

  • 线程 A 读 count=0 → 准备改成 1,但还没写回;
  • 线程 B 此时也读 count=0 → 改成 1 并写回;
  • 线程 A 再写回 1;
  • 最终 count=1,但预期是 2,数据错乱。

互斥锁的作用就是保证同一时间只有一个线程能操作共享资源(即 "互斥"),把 "读→改→写" 变成原子操作(不可分割)。

2. 互斥锁的核心特性
  • 互斥性:同一时刻,只有一个线程能持有锁,其他线程尝试加锁会阻塞;
  • 原子性:加锁 / 解锁操作是原子的,不会被 CPU 调度打断;
  • 非递归:同一个线程不能重复加锁(否则会死锁);
  • 释放原则:只有持有锁的线程能解锁,其他线程解锁会报错。
3. 形象比喻

互斥锁 = 卫生间的门锁:

  • 线程要使用卫生间(操作共享资源),必须先 "上锁"(加锁);
  • 其他线程想进,只能在门口等(阻塞);
  • 用完后必须 "解锁"(释放锁),否则其他线程永远进不去(死锁);
  • 同一个人不能连续锁两次门(非递归)。

二、Linux 互斥锁核心函数(pthread 库)

Linux 互斥锁的函数都在 pthread 库中,编译时需加 -lpthread,核心类型是 pthread_mutex_t

1. 互斥锁初始化:pthread_mutex_init()

功能:初始化一个互斥锁。

函数原型

复制代码
#include <pthread.h>

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);

参数解释

  • mutex:指向 pthread_mutex_t 类型的指针,用于存储互斥锁。
  • attr:指向 pthread_mutexattr_t 类型的指针,用于指定互斥锁属性。通常设置为 NULL 表示使用默认属性。
  • 返回值:成功返回 0,失败返回错误码。
2. 加锁:pthread_mutex_lock()(阻塞式)

功能 :尝试获取互斥锁 ,若锁已被其他线程持有,则当前线程阻塞(暂停运行),直到锁被释放。

函数原型

复制代码
int pthread_mutex_lock(pthread_mutex_t *mutex);

参数mutex:指向 pthread_mutex_t 类型的指针,用于获取互斥锁。

返回值:成功返回 0,失败返回错误码。

3. 尝试加锁:pthread_mutex_trylock()(非阻塞式)

功能 :尝试获取互斥锁,若锁已被持有,不阻塞 ,直接返回错误码(EBUSY),线程可继续执行其他逻辑。

函数原型

复制代码
int pthread_mutex_trylock(pthread_mutex_t *mutex);

参数:mutex:指向 pthread_mutex_t 类型的指针,用于尝试获取互斥锁。

适用场景:不想让线程阻塞,比如 "能拿到锁就操作资源,拿不到就先做别的事"。

4. 解锁:pthread_mutex_unlock()

功能释放持有的互斥锁,唤醒等待该锁的线程(由内核调度器选择一个线程获得锁)。

函数原型

复制代码
int pthread_mutex_unlock(pthread_mutex_t *mutex);

参数:mutex:指向 pthread_mutex_t 类型的指针,用于释放互斥锁。

注意

  • 只有持有锁的线程 能解锁,其他线程解锁会返回 EPERM 错误;
  • 解锁前必须确保已加锁,否则会导致未定义行为(如死锁)。
5. 销毁互斥锁:pthread_mutex_destroy()

功能:销毁互斥锁,释放相关资源(仅适用于动态初始化的锁)。

函数原型

复制代码
int pthread_mutex_destroy(pthread_mutex_t *mutex);

参数:mutex:指向 pthread_mutex_t 类型的指针,用于销毁互斥锁。

注意 :销毁前必须确保锁已解锁,且没有线程在等待该锁,否则会返回 EBUSY 错误。

三、互斥锁的常见问题与注意事项

1. 死锁(最常见问题)

死锁场景

  • 场景 1:同一个线程重复加锁(pthread_mutex_lock 调用两次);
  • 场景 2:线程 A 持有锁 1,等待锁 2;线程 B 持有锁 2,等待锁 1。

解决方法

  • 避免重复加锁;
  • 统一锁的获取顺序(如先获取锁 1,再获取锁 2);
  • pthread_mutex_trylock 替代阻塞加锁,超时则释放已持有的锁。
2. 锁的粒度
  • 粗粒度锁:一个锁保护所有共享资源(简单,但并发效率低);
  • 细粒度锁:多个锁分别保护不同的共享资源(并发效率高,但易出死锁);
  • 建议:根据业务场景选择,新手先从粗粒度锁开始。
3. 互斥锁 vs 自旋锁
类型 特点 适用场景
互斥锁 加锁失败则阻塞(让出 CPU) 锁持有时间长(如 IO 操作)
自旋锁 加锁失败则循环重试(占用 CPU) 锁持有时间极短(如简单计数)

四、互斥锁属性(进阶)

通过 pthread_mutexattr_t 可自定义锁属性,常见属性:

  1. 普通锁(默认):重复加锁死锁,解锁非持有线程未定义;
  2. 错误检查锁 :重复加锁返回 EDEADLK,解锁非持有线程返回 EPERM
  3. 递归锁:允许同一线程多次加锁(需对应次数解锁);

示例:初始化递归锁

复制代码
pthread_mutex_t mutex;
pthread_mutexattr_t attr;

// 初始化属性
pthread_mutexattr_init(&attr);
// 设置为递归锁
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
// 用自定义属性初始化锁
pthread_mutex_init(&mutex, &attr);

// 递归加锁(允许)
pthread_mutex_lock(&mutex);
pthread_mutex_lock(&mutex);
count += 10;
// 需解锁两次
pthread_mutex_unlock(&mutex);
pthread_mutex_unlock(&mutex);

// 销毁属性和锁
pthread_mutexattr_destroy(&attr);
pthread_mutex_destroy(&mutex);

五、总结

  1. 互斥锁核心作用:保证同一时间只有一个线程操作共享资源,解决竞态条件;
  2. 核心函数
    • 初始化:pthread_mutex_init / PTHREAD_MUTEX_INITIALIZER
    • 加锁:pthread_mutex_lock(阻塞)/ pthread_mutex_trylock(非阻塞);
    • 解锁:pthread_mutex_unlock
    • 销毁:pthread_mutex_destroy
  3. 关键注意点
    • 避免死锁(重复加锁、交叉锁);
    • 解锁前必须加锁,且只有持有锁的线程能解锁;
    • 根据锁持有时间选择互斥锁(长)或自旋锁(短)。
  4. 进程间的互斥锁
  • 需要注意的是互斥锁(Mutex)主要用于多线程环境中的同步,但在多进程环境中使用互斥锁需要特别注意。标准的 POSIX 互斥锁(pthread_mutex_t)是线程私有的,不能直接用于多进程之间的同步。这是因为每个进程都有独立的地址空间,互斥锁的状态信息(如锁的持有者、锁的状态等)存储在进程的内存中,无法在不同的进程之间共享。

  • 如果需要在多个进程之间共享互斥锁,必须使用 PTHREAD_PROCESS_SHARED 属性,并且互斥锁需要存储在共享内存中,这种方式过于冗杂。实际使用时,还是建议使用信号量进行资源多进程资源管理。

相关推荐
草莓熊Lotso1 小时前
Qt文件操作:QFile读写全解析
运维·开发语言·c++·人工智能·qt
一路往蓝-Anbo1 小时前
第 10 章:OpenAMP 实战——构建 M33 与 Linux 的 RPMsg 消息隧道
linux·运维·服务器·驱动开发·stm32·单片机·嵌入式硬件
Starry_hello world1 小时前
Linux 网络(6)
linux·运维·网络
Drifter_yh1 小时前
「JVM」 Java 类加载机制与双亲委派模型深度解析
java·开发语言·jvm
Starry_hello world1 小时前
Linux 网络(5)
linux·网络·智能路由器
xyq20241 小时前
《Ionic 卡片:设计理念与实战指南》
开发语言
wjs20242 小时前
《Chart.js 环形图》
开发语言
水木姚姚2 小时前
string类(C++)
开发语言·c++·windows·vscode·开发工具
方便面不加香菜2 小时前
C++ 类和对象(一)
开发语言·c++