一个线程死锁问题的分析
 
    客户报过来一个问题,服务器运行一周左右就会停止响应,有时候甚至两天就不响应了,并发用户量并不大,重启服务后又工作正常。每当遇到这种问题时就有点儿棘手。一是这种问题的复现条件不好确定,另一方面,即使确定了条件,对于多线程的服务程序,也不好调试。我遇到过的这种问题,大部分是靠读代码分析出来一个可能的原因列表,然后一一验证,最终找到真正的问题所在。
    首先拿到服务运行日志发现没有任何错误消息。
    抓到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;
}
    这段代码先从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等待。可是第一个线程等待了,其他的工作线程就都等待了。没有线程处理新的请求了,服务器也就不响应了。
    找到了问题原因,就好修改了。