在 Linux 系统编程中,嵌套锁(Nested Lock) 通常指允许同一线程多次获取同一个锁的机制(即 递归锁,Recursive Mutex)。这种设计用于解决函数调用链中需要重复加锁的场景(例如递归函数、多层函数嵌套访问共享资源),避免同一线程因重复加锁导致的死锁。以下是详细分析和实现指南:
1. 嵌套锁的核心概念
-
递归加锁:同一线程可以多次对同一锁调用
lock
,但必须调用相同次数的unlock
。 -
锁计数器:递归锁内部维护一个计数器,记录当前线程的加锁次数。
-
应用场景:
-
递归函数访问共享资源。
-
模块化代码中多个函数嵌套调用同一锁。
-
2. Linux 递归锁的实现(POSIX 线程库)
Linux 通过 pthread_mutex
的 递归属性 实现嵌套锁:
步骤 1:设置递归属性
#include <pthread.h>
int main() {
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_mutexattr_destroy(&attr);
// ... 使用锁 ...
pthread_mutex_destroy(&mutex);
return 0;
}
步骤 2:递归加锁示例
void recursive_function(pthread_mutex_t *mutex, int depth) {
pthread_mutex_lock(mutex); // 第一次加锁
if (depth > 0) {
recursive_function(mutex, depth - 1); // 递归调用,再次加锁
}
pthread_mutex_unlock(mutex); // 每次解锁
}
int main() {
pthread_mutex_t mutex;
// 初始化递归锁(代码同上)
recursive_function(&mutex, 3); // 嵌套加锁3次,解锁3次
pthread_mutex_destroy(&mutex);
return 0;
}
3. 嵌套锁的注意事项
(1) 加锁与解锁必须严格配对
-
每次
lock
必须对应一次unlock
,否则其他线程无法获取锁。 -
错误示例:
pthread_mutex_lock(&mutex); // 加锁1 pthread_mutex_lock(&mutex); // 加锁2(合法,因为这是递归锁) pthread_mutex_unlock(&mutex); // 解锁1 // 忘记第二次解锁!锁仍被持有,其他线程将阻塞。
(2) 避免跨线程解锁
-
只有锁的持有者线程可以解锁,其他线程解锁会导致未定义行为。
// 线程A pthread_mutex_lock(&mutex); // 线程B pthread_mutex_unlock(&mutex); // 错误!EPERM 错误
(3) 性能开销
-
递归锁比普通互斥锁略慢(需维护计数器),非必要场景避免滥用。
4. 嵌套锁的典型应用场景
场景 1:递归函数访问共享资源
// 递归遍历链表并修改节点
void traverse_list(Node *node, pthread_mutex_t *mutex) {
if (node == NULL) return;
pthread_mutex_lock(mutex);
modify_node(node); // 修改当前节点
traverse_list(node->next, mutex); // 递归调用(再次加锁)
pthread_mutex_unlock(mutex);
}
场景 2:模块化代码中的锁传递
// 模块A的函数
void func_a(pthread_mutex_t *mutex) {
pthread_mutex_lock(mutex);
func_b(mutex); // 调用模块B的函数(需要同一锁)
pthread_mutex_unlock(mutex);
}
// 模块B的函数
void func_b(pthread_mutex_t *mutex) {
pthread_mutex_lock(mutex); // 再次加锁(合法)
// 操作共享资源
pthread_mutex_unlock(mutex);
}
6. 递归锁与普通锁的对比
特性 | 递归锁 | 普通锁(非递归) |
---|---|---|
同一线程重复加锁 | 允许(计数器递增) | 导致死锁(如 PTHREAD_MUTEX_ERRORCHECK 返回 EDEADLK ) |
性能 | 略低(维护计数器) | 较高 |
适用场景 | 嵌套函数/递归访问共享资源 | 简单临界区 |
7. 综合示例:线程安全的递归目录遍历
#include <stdio.h>
#include <dirent.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
pthread_mutex_t mutex;
void list_dir(const char *path, int depth) {
DIR *dir = opendir(path);
if (!dir) return;
struct dirent *entry;
while ((entry = readdir(dir)) != NULL) {
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
continue;
pthread_mutex_lock(&mutex); // 加锁保护打印操作
for (int i = 0; i < depth; i++) printf(" ");
printf("%s\n", entry->d_name);
pthread_mutex_unlock(&mutex);
if (entry->d_type == DT_DIR) {
char subpath[1024];
snprintf(subpath, sizeof(subpath), "%s/%s", path, entry->d_name);
list_dir(subpath, depth + 1); // 递归调用,再次加锁
}
}
closedir(dir);
}
int main() {
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init(&mutex, &attr);
pthread_mutexattr_destroy(&attr);
list_dir(".", 0); // 从当前目录开始遍历
pthread_mutex_destroy(&mutex);
return 0;
}