1、背景
在多线程编程中,pthread_mutexattr_t 属性对象用于定制互斥锁的行为。其中最常用的三个属性设置函数是:
- pthread_mutexattr_settype -- 设置锁类型(type)
- pthread_mutexattr_setpshared -- 设置进程共享(pshared)
- pthread_mutexattr_setrobust -- 设置鲁棒性(robust)
三者控制的是互斥锁完全不同维度的特性。下面逐一深入解析。
2、pthread_mutexattr_settype
这个函数用于设置锁的类型,主要控制互斥锁在同一线程内重入、死锁检测、解锁权限检查等方面的行为,其常用属性如下:
- PTHREAD_MUTEX_NORMAL(快速锁):无检查,重入导致死锁
- PTHREAD_MUTEX_ERRORCHECK(错误检查锁):检测重入(返回 EDEADLK)和解锁未持有锁(返回 EPERM)
- PTHREAD_MUTEX_RECURSIVE(递归锁):允许同一线程多次加锁,内部维护计数
- PTHREAD_MUTEX_DEFAULT:实现定义(Linux 下同 NORMAL)
| 类型宏 | 行为描述 | 适用场景 |
|---|---|---|
| PTHREAD_MUTEX_NORMAL | 无错误检查;同一线程重复加锁 → 死锁;解锁未加锁的锁 → 未定义行为 | 对性能要求极致,且代码逻辑绝对正确 |
| PTHREAD_MUTEX_ERRORCHECK | 检测重入:返回 EDEADLK;检测解锁未持有锁:返回 EPERM | 调试阶段,或希望尽早暴露锁误用的生产代码 |
| PTHREAD_MUTEX_RECURSIVE | 允许同一线程多次加锁,内部维护计数;解锁次数需等于加锁次数 | 递归函数中需要加锁,或复杂嵌套调用 |
| PTHREAD_MUTEX_DEFAULT | 实现定义(Linux 同 NORMAL,macOS 可能为 ERRORCHECK) | 不推荐依赖,最好显式指定 |
主要应用场景如下:
- 调试阶段使用 ERRORCHECK 暴露错误
- 递归函数需要锁时使用 RECURSIVE
- 性能极致优化时使用 NORMAL
cpp
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK);
pthread_mutex_t mutex;
pthread_mutex_init(&mutex, &attr);
// 第一次加锁成功
pthread_mutex_lock(&mutex);
// 第二次加锁(同一线程)→ 返回 EDEADLK,不会死锁
int ret = pthread_mutex_lock(&mutex);
if (ret == EDEADLK) {
printf("Detected self-deadlock!\n");
}
// 正常解锁
pthread_mutex_unlock(&mutex);
// 尝试解锁一个已经解锁的锁 → 返回 EPERM
ret = pthread_mutex_unlock(&mutex);
if (ret == EPERM) {
printf("Unlock without ownership detected!\n");
}
3、pthread_mutexattr_setpshared -- 进程共享
主要用于设置互斥锁的作用域:是仅用于同一进程内的线程,还是可以用于多个进程之间同步,常用属性值如下:
- PTHREAD_PROCESS_PRIVATE(默认值):互斥锁只在创建它的进程内有效,不能跨进程使用
- PTHREAD_PROCESS_SHARED:互斥锁可以放在共享内存中(如 mmap、shm_open),供多个进程共同使用
应用场景如下: - 多进程共享数据(如共享内存数据库、进程间通信)时,需要 PROCESS_SHARED
- 普通的线程间同步,使用默认的 PRIVATE 即可,性能更好
对于这个函数有一个需要非常注意的地方: - 使用 PROCESS_SHARED 时,必须确保存放互斥锁的内存区域是共享的,并且所有进程都能访问
- 跨进程使用时,还需要考虑鲁棒性
4、pthread_mutexattr_setrobust -- 鲁棒性
这个函数的作用是设置互斥锁的鲁棒性,当持有互斥锁的进程意外终止(崩溃、被 kill 等)时,其他等待该锁的进程应该如何处理,常用取值如下:
- PTHREAD_MUTEX_STALLED(默认值):如果锁的持有者崩溃,锁会永远处于"被锁定"状态,后续任何尝试加锁的线程/进程将永久阻塞(死锁)
- PTHREAD_MUTEX_ROBUST:如果持有者崩溃,后续第一个成功加锁的线程会收到错误码 EOWNERDEAD,表示"之前的拥有者已死亡"。该线程有责任清理互斥锁保护的状态,并调用 pthread_mutex_consistent 使锁恢复正常,然后继续使用
cpp
// 假设 mutex 是跨进程鲁棒锁(PROCESS_SHARED + ROBUST)
int ret = pthread_mutex_lock(mutex);
if (ret == EOWNERDEAD) {
// 上一个持有者已经死亡
// 1. 恢复互斥锁所保护的共享数据的一致性
recover_shared_state();
// 2. 标记锁为一致状态
pthread_mutex_consistent(mutex);
// 现在当前线程拥有这个锁,可以安全操作
} else if (ret == 0) {
// 正常获取锁
}
5、常见问题
5.1、ERRORCHECK 锁就能检测所有死锁吗
只能检测同一线程的自重入死锁,无法检测线程 A 等 B、B 等 A 的循环等待。后者需用静态分析或超时机制
5.2、设置了 ROBUST 后,锁就自动恢复吗?
ROBUST 只让后续加锁者知道"原拥有者死了",但仍需要手动调用 pthread_mutex_consistent 并修复共享数据,锁才会恢复可用
5.3、跨进程锁必须用 ROBUST吗?
非强制,但如果不用 ROBUST,一个进程崩溃会导致其他进程永远等锁(STALLED 行为)。在要求高可用的系统中,ROBUST 是强烈推荐的
6、总结
- settype 回答:同一线程能否重复加锁?解锁检查所有权吗?
- setpshared 回答:锁只给自己线程用,还是给别的进程用?
- setrobust 回答:如果锁的主人死了,其他等待者怎么办?