一个线程死锁问题的分析
客户报过来一个问题,服务器运行一周左右就会停止响应,有时候甚至两天就不响应了,并发用户量并不大,重启服务后又工作正常。每当遇到这种问题时就有点儿棘手。一是这种问题的复现条件不好确定,另一方面,即使确定了条件,对于多线程的服务程序,也不好调试。我遇到过的这种问题,大部分是靠读代码分析出来一个可能的原因列表,然后一一验证,最终找到真正的问题所在。
首先拿到服务运行日志发现没有任何错误消息。
抓到trace发现,每个线程都没有报错误,但是到了某一时刻就全部停止工作了。首先想到会不会和时间有关系,询问客户,停止响应的时间并不固定。
拿到服务停止时的各个线程的调用栈,发现所有的工作线程都停留在pthread_cond_wait上,看来线程之间同步处理有问题。
先尝试复现一下,看看和特定的数据有没有关系。同事写了个多线程的客户端,并发查询服务器,问题很快就出现了,这也暴漏了我们在测试方面的不足。
开始分析代码,这段代码其实有很多处理,非常复杂,写了个简单的示例代码:
#define READ_LOCK 1
int get_data_from_cache(int index, ENTRYDATA** entry) {
ENTRYDATA* ed = NULL;
pthread_mutex_lock (global_data->lock_var);
ed = find_entry_list(global_data->list, index);
if (!ed) return 1;
if (ed->locked_state != READ_LOCK)
ed->locked_state = READ_LOCK;
else
++ed->locked_state;
if (!ed->password_clear)
ed->password_clear = get_clear_password(ed->password);
pthread_mutex_unlock (global_data->lock_var);
*entry = ed;
return 0;
}
int release_lock_for_entry(ENTRYDATA* entry, type) {
ENTRYDATA* ed = entry;
bool broadcast = true;
pthread_mutex_lock (global_data->lock_var);
while (1) {
if (type == 1) {
if (ed->locked_state != READ_LOCK)
pthread_cond_wait(global_data->cond_var);
else {
if (ed->password_clear) {
free (ed->password_clear);
ed->password_clear = NULL;
}
--ed->locked_state;
break;
}
}
}
pthread_cond_broadcast(global_data->cond_var);
pthread_mutex_unlock (global_data->lock_var);
return 0;
}
int thread_main() {
ENTRYDATA* ed = NULL;
get_data_from_cache(5, &ed);
// process the data
release_lock_for_entry(ed, 1);
return 0;
}
int get_data_from_cache(int index, ENTRYDATA** entry) {
ENTRYDATA* ed = NULL;
pthread_mutex_lock (global_data->lock_var);
ed = find_entry_list(global_data->list, index);
if (!ed) return 1;
if (ed->locked_state != READ_LOCK)
ed->locked_state = READ_LOCK;
else
++ed->locked_state;
if (!ed->password_clear)
ed->password_clear = get_clear_password(ed->password);
pthread_mutex_unlock (global_data->lock_var);
*entry = ed;
return 0;
}
int release_lock_for_entry(ENTRYDATA* entry, type) {
ENTRYDATA* ed = entry;
bool broadcast = true;
pthread_mutex_lock (global_data->lock_var);
while (1) {
if (type == 1) {
if (ed->locked_state != READ_LOCK)
pthread_cond_wait(global_data->cond_var);
else {
if (ed->password_clear) {
free (ed->password_clear);
ed->password_clear = NULL;
}
--ed->locked_state;
break;
}
}
}
pthread_cond_broadcast(global_data->cond_var);
pthread_mutex_unlock (global_data->lock_var);
return 0;
}
int thread_main() {
ENTRYDATA* ed = NULL;
get_data_from_cache(5, &ed);
// process the data
release_lock_for_entry(ed, 1);
return 0;
}
这段代码先从cache中把数据取出来,同时加锁(locked_state),进行处理后再释放锁。pthread_cond_wait使线程进入等待状态,当有另一个线程调用pthread_cond_signal或者pthread_cond_broadcast时,等待的线程才会被激活。
代码中对应的pthread_cond_broadcast是有的,而且正常流程下会执行到,没有问题。
然后看什么条件下会进入pthread_cond_wait,当locked_state不等于READ_LOCK时,也就是当有多个线程同时读取同一条entry时。但是第一个线程会调用pthread_cond_broadcast,触发第二个线程运行,然后第二个线程调用pthread_cond_broadcast,触发第三个线程运行,如此反复,不会所有的线程都等待。
如果第一个线程也进入等待状态,那么所有的线程就都只能等待,服务器就不能响应了。有什么原因导致以一个线程直接进入等待状态呢?也是locked_state不等于READ_LOCK时。这是第一个线程,而且对于locked_state的修改都有mutex封装。不会同时又多个线程更改locked_state.
多看几遍代码发现,修改locked_state和判断locked_state虽然都在mutex中,但是它们在不同的函数里,两个函数之间还有一块代码不在mutex的保护之中。设想这样一种情况,当第一个线程取出entry并修改locked_state后,开始处理数据,这时第二个线程读取数据,同时修改了locked_state,此时locked_state不再是READ_LOCK了,然后第一个线程处理完毕开始释放锁,由于locked_state不是READ_LOCK,所以第一个线程开始进入pthread_cond_wait等待。可是第一个线程等待了,其他的工作线程就都等待了。没有线程处理新的请求了,服务器也就不响应了。
找到了问题原因,就好修改了。
转载于:https://blog.51cto.com/lishengli/195574