CONC.DL
死锁
多线程程序中的死锁指的是两个或多个线程相互阻止,各自等待对方完成的情况。这通常由无序的锁争用引起,其中,每个线程均保留有另一个线程所需的资源,因此只有在获得资源后,两者才能解除阻止。
通常,死锁由无序保留的循环锁链引起。例如,线程 1 所执行代码的锁保留模式要求锁 A 先于锁 B 保留,同时,线程 2 所执行代码(可能相同)的锁保留模式要求锁 B 先于锁 A 保留。如果这两个线程在执行过程中发生冲突,线程 1 将可能在线程 2 已保留锁 B 后请求该锁。线程 2 可能请求锁 A,但会受到阻止,因为线程 1 已经保留该锁。
CONC.DL 检查器可查找死锁实例。
漏洞与风险
此类型的循环锁引用可能导致程序死机、UI 无效、设备无响应以及类似问题。现场调试这些问题很可能花费开发人员数周时间,同时也会让客户沮丧。
缓解与预防
为了帮助避免锁争用:
- 请尝试让代码的锁定部分尽可能小和简单以便于理解。
- 请勿锁定可能导致数据竞争等并发问题的代码部分。
- 务必避免循环等待的状况。
- 如果使用多个锁(通常在升级保护模式下),务必确保在每种情况下已执行完全相同的升级操作。
漏洞代码示例 1
#include <stdio.h>
#include <pthread.h>
static pthread_mutex_t A, B;
void *printmsg1(void *msg) {
pthread_mutex_lock(&A); // Execution step 1
pthread_mutex_lock(&B); // Execution step 3
printf("printmsg1\n");
pthread_mutex_unlock(&B);
pthread_mutex_unlock(&A);
return 0;
}
void *printmsg2(void *msg) {
pthread_mutex_lock(&B); // Execution step 2
pthread_mutex_lock(&A);
printf("printmsg2\n");
pthread_mutex_unlock(&A);
pthread_mutex_unlock(&B);
return 0;
}
int main(int argc, char**argv) {
pthread_t pt1, pt2;
pthread_mutex_init(&A, NULL);
pthread_mutex_init(&B, NULL);
pthread_create(&pt1,0, printmsg1, NULL);
pthread_create(&pt2,0, printmsg2, NULL);
pthread_join(pt1,0);
pthread_join(pt2,0);
pthread_mutex_destroy(&A);
pthread_mutex_destroy(&B);
return 0;
}
Klocwork 报告了第 6 行中使用全局互斥 A 和 B 的两个线程之间可能发生死锁。主例程创建并启动在函数 printmsg1 和 printmsg2 中定义的两个线程。第一个线程在第 7 行等待获取互斥 B 上的锁,同时保留互斥 A 上的锁。第二个线程在第 16 行等待获取互斥 A 上的锁,同时保留互斥 B 上的锁。两个线程之间的这一循环锁链便可导致死锁。
修正代码示例 1
#include <stdio.h>
#include <pthread.h>
static pthread_mutex_t A, B;
void *printmsg1(void *msg) {
pthread_mutex_lock(&A);
pthread_mutex_lock(&B);
printf("printmsg1\n");
pthread_mutex_unlock(&B);
pthread_mutex_unlock(&A);
return 0;
}
void *printmsg2(void *msg) {
pthread_mutex_lock(&A);
pthread_mutex_lock(&B);
printf("printmsg2\n");
pthread_mutex_unlock(&B);
pthread_mutex_unlock(&A);
return 0;
}
int main(int argc, char**argv) {
pthread_t pt1, pt2;
pthread_mutex_init(&A, NULL);
pthread_mutex_init(&B, NULL);
pthread_create(&pt1,0, printmsg1, NULL);
pthread_create(&pt2,0, printmsg2, NULL);
pthread_join(pt1,0);
pthread_join(pt2,0);
pthread_mutex_destroy(&A);
pthread_mutex_destroy(&B);
return 0;
}
如果每个进程中均使用多个互斥进行同步,这些互斥应以相同顺序锁定。在该修正代码示例中,已更改第 15、16、18 和 19 行的锁和解锁顺序,使得它们与 printmsg2 和 printmsg1 中相同,以确保不会发生循环链。
漏洞代码示例 2
#include <stdio.h>
#include <pthread.h>
static pthread_mutex_t A, B;
static pthread_cond_t cond;
static int count;
void *f1(void *msg) {
pthread_mutex_lock(&A);
pthread_mutex_lock(&B);
while (count) {
pthread_cond_wait(&cond, &B);
}
pthread_mutex_unlock(&B);
pthread_mutex_unlock(&A);
return 0;
}
void *f2(void *msg) {
pthread_mutex_lock(&A);
pthread_mutex_lock(&B);
count--;
pthread_cond_broadcast(&cond);
pthread_mutex_unlock(&B);
pthread_mutex_unlock(&A);
return 0;
}
int main(int argc, char**argv) {
pthread_t pt1, pt2;
pthread_mutex_init(&A, NULL);
pthread_mutex_init(&B, NULL);
pthread_cond_init(&cond, NULL);
count = 1;
pthread_create(&pt1,0, f1, NULL);
pthread_create(&pt2,0, f2, NULL);
pthread_join(pt1, NULL);
pthread_join(pt2, NULL);
pthread_mutex_destroy(&A);
pthread_mutex_destroy(&B);
pthread_cond_destroy(&cond);
return 0;
}
Klocwork 报告了第 11 行中使用全局互斥 A 和条件变量 cond 的两个线程之间可能发生死锁。第一个线程可能在第 11 行无限期地等待条件变量 cond,同时保留互斥 A 上的锁。第二个线程在第 18 行等待获取互斥 A 上的锁,以更改全局变量 count 并向条件变量 cond 发送一个信号。
修正代码示例 2
#include <stdio.h>
#include <pthread.h>
static pthread_mutex_t A, B;
static pthread_cond_t cond;
static int count;
void *f1(void *msg) {
pthread_mutex_lock(&B);
while (count) {
pthread_cond_wait(&cond, &B);
}
pthread_mutex_unlock(&B);
return 0;
}
void *f2(void *msg) {
pthread_mutex_lock(&A);
pthread_mutex_lock(&B);
count--;
pthread_cond_broadcast(&cond);
pthread_mutex_unlock(&B);
pthread_mutex_unlock(&A);
return 0;
}
int main(int argc, char**argv) {
pthread_t pt1, pt2;
pthread_mutex_init(&A, NULL);
pthread_mutex_init(&B, NULL);
pthread_cond_init(&cond, NULL);
count = 1;
pthread_create(&pt1,0, f1, NULL);
pthread_create(&pt2,0, f2, NULL);
pthread_join(pt1, NULL);
pthread_join(pt2, NULL);
pthread_mutex_destroy(&A);
pthread_mutex_destroy(&B);
pthread_cond_destroy(&cond);
return 0;
}
在条件等待函数之前,所设置的锁应仅包含在相同函数中需要并解锁的互斥。在该修正代码示例中,等待函数 pthread_cond_wait 之前的互斥 A 锁已删除,因为不需要。移除互斥 A 锁可消除第 10 行报告死锁的可能性。
相关检查器
外部指导
扩展
此检查器可进行扩展。相关的知识库记录类型包括:
- CONC.LOCK
- CONC.LOCK.TRY
- CONC.UNLOCK
- CONC.CONDSIGNAL
- CONC.CONDWAIT
有关详情,请参阅调整 C/C++ 分析。