面试题答案
一键面试互斥锁使用策略
-
锁的初始化:
- 在每个模块初始化阶段,使用
pthread_mutex_init
函数初始化各自的互斥锁。例如:
pthread_mutex_t module1_mutex; pthread_mutex_init(&module1_mutex, NULL);
- 确保在整个应用程序生命周期内,每个互斥锁只初始化一次。
- 在每个模块初始化阶段,使用
-
嵌套规则:
- 定义全局的锁获取顺序,所有模块都遵循这个顺序获取锁。例如,按照模块的编号或者模块名的字典序。
- 假设模块1、模块2和模块3,模块1编号最小,模块3编号最大。当模块1调用模块2,模块2调用模块3时,获取锁的顺序始终是模块1的锁 -> 模块2的锁 -> 模块3的锁。
- 在获取新的锁之前,检查是否已经持有需要获取的锁。如果已经持有,直接跳过获取操作,避免重复获取。
-
异常处理:
- 在获取锁时,使用
pthread_mutex_lock
函数,该函数返回错误码。如果返回非零值,根据错误码进行相应处理。例如,如果错误码表示资源暂时不可用(EAGAIN
),可以选择重试一定次数或者等待一段时间后再试。
int ret = pthread_mutex_lock(&module1_mutex); if (ret != 0) { if (ret == EAGAIN) { // 重试逻辑 int retry_count = 5; while (retry_count > 0) { ret = pthread_mutex_lock(&module1_mutex); if (ret == 0) { break; } retry_count--; sleep(1); } if (retry_count == 0) { // 处理获取锁失败的情况,如记录日志,抛出异常等 perror("Failed to lock module1_mutex after retries"); // 这里可以选择终止当前线程或者进行其他合适的操作 } } else { // 处理其他错误 perror("Failed to lock module1_mutex"); // 同样可以选择终止当前线程或者进行其他合适的操作 } }
- 在解锁时,使用
pthread_mutex_unlock
函数,同样检查返回值。如果返回非零值,表明解锁操作出现错误,进行相应处理,如记录日志。
ret = pthread_mutex_unlock(&module1_mutex); if (ret != 0) { perror("Failed to unlock module1_mutex"); }
- 在获取锁时,使用
策略分析
-
可扩展性:
- 优点:通过定义全局的锁获取顺序,随着模块数量的增加,新模块可以按照既定顺序获取锁,不需要额外的复杂协调机制。只要遵循锁获取顺序,就可以很容易地添加新模块,可扩展性较好。
- 缺点:如果模块间的调用关系变得非常复杂,全局锁获取顺序的维护可能会变得困难,尤其是在动态添加或删除模块的场景下。
-
性能:
- 优点:在锁获取顺序明确的情况下,线程等待锁的时间可以得到一定控制。因为所有线程都按相同顺序获取锁,减少了锁竞争的随机性,有利于提高并发性能。
- 缺点:严格的锁获取顺序可能导致一些不必要的等待。例如,模块1本来不需要模块3的锁,但因为要遵循锁获取顺序,必须先获取模块2和模块3的锁,这可能会增加一些不必要的开销。
-
死锁风险:
- 优点:全局锁获取顺序的严格遵循可以有效避免经典的死锁场景,即多个线程循环等待对方持有的锁。只要所有模块都按照这个顺序获取锁,死锁发生的概率会大大降低。
- 缺点:如果在某些特殊情况下,如异常处理不当导致锁没有正确释放,或者代码中存在违反锁获取顺序的隐藏逻辑,仍然可能出现死锁。
伪代码示例
// 模块1
void module1_function() {
// 初始化模块1的互斥锁
pthread_mutex_t module1_mutex;
pthread_mutex_init(&module1_mutex, NULL);
// 获取模块1的锁
int ret = pthread_mutex_lock(&module1_mutex);
if (ret != 0) {
// 处理获取锁失败
handle_lock_failure(ret);
}
// 调用模块2
module2_function();
// 释放模块1的锁
ret = pthread_mutex_unlock(&module1_mutex);
if (ret != 0) {
// 处理解锁失败
handle_unlock_failure(ret);
}
}
// 模块2
void module2_function() {
// 初始化模块2的互斥锁
pthread_mutex_t module2_mutex;
pthread_mutex_init(&module2_mutex, NULL);
// 获取模块2的锁
int ret = pthread_mutex_lock(&module2_mutex);
if (ret != 0) {
// 处理获取锁失败
handle_lock_failure(ret);
}
// 调用模块3
module3_function();
// 释放模块2的锁
ret = pthread_mutex_unlock(&module2_mutex);
if (ret != 0) {
// 处理解锁失败
handle_unlock_failure(ret);
}
}
// 模块3
void module3_function() {
// 初始化模块3的互斥锁
pthread_mutex_t module3_mutex;
pthread_mutex_init(&module3_mutex, NULL);
// 获取模块3的锁
int ret = pthread_mutex_lock(&module3_mutex);
if (ret != 0) {
// 处理获取锁失败
handle_lock_failure(ret);
}
// 模块3的业务逻辑
// 释放模块3的锁
ret = pthread_mutex_unlock(&module3_mutex);
if (ret != 0) {
// 处理解锁失败
handle_unlock_failure(ret);
}
}
// 处理获取锁失败的函数
void handle_lock_failure(int ret) {
if (ret == EAGAIN) {
// 重试逻辑
int retry_count = 5;
while (retry_count > 0) {
// 尝试重新获取锁
ret = pthread_mutex_lock(&module1_mutex);
if (ret == 0) {
break;
}
retry_count--;
sleep(1);
}
if (retry_count == 0) {
// 记录日志
log_message("Failed to lock after retries");
// 可以选择终止当前线程
pthread_exit(NULL);
}
} else {
// 记录其他错误日志
log_message("Failed to lock with error code: %d", ret);
// 可以选择终止当前线程
pthread_exit(NULL);
}
}
// 处理解锁失败的函数
void handle_unlock_failure(int ret) {
// 记录日志
log_message("Failed to unlock with error code: %d", ret);
// 可以进行其他处理,如尝试重新解锁等
}
// 记录日志的函数
void log_message(const char* format, ...) {
// 实现日志记录逻辑
}