记录线程退出(thread_cancel)导致的死锁问题
源代码:
pthread_t tid1, tid2, tid3;
extern void *log_test_thread_handler(void *arg);
pthread_create(&tid1, NULL, log_test_thread_handler, "xxxxxxx");
pthread_create(&tid2, NULL, log_test_thread_handler, "OOOOOOO");
pthread_create(&tid3, NULL, log_test_thread_handler, "MMMMMMM");
sleep(1);
log_set_call(false);
sleep(1);
log_trace("pthread 1 cancel: %d", pthread_cancel(tid1));
pthread_join(tid1, NULL);
log_trace("pthread 2 cancel: %d", pthread_cancel(tid2));
log_trace("pthread 3 cancel: %d", pthread_cancel(tid3));
pthread_join(tid2, NULL);
pthread_join(tid3, NULL);
}
void log_test_thread_exit_handler(void *arg){
log_debug("%s", __FUNCTION__);
}
void *log_test_thread_handler(void *arg)
{
pthread_cleanup_push(log_test_thread_exit_handler, NULL);
char *buf = (char *)arg;
while (1) {
usleep(10);
pthread_testcancel(); // use pthread_testcancel could be successful. because have not enough time to response.
log_info("hello world! %s", buf);
}
pthread_cleanup_pop(1);
}
分别创建三个线程,在线程中使用日志库分别打印不同的内容。
延迟2s后取消线程并退出。反复运行上面的代码。有概率出现阻塞的情况,经分析发现是死锁。将while(1)中的延时函增加,加到一定程度后,基本不会出现阻塞卡死的情况。猜测是,彻底取消线程,延时都还没完毕,就不会调用到log_info,那么就不会产生死锁。
分析
这里使用pthread_cancel可能会导致死锁。当执行pthread_cancel函数后,线程执行函数达取消点时,会执行退出,但是这个时候,还有可能会执行log_info,log_info内部使用互斥锁来保证数据一致性。进入该函数,首先会先获得锁,打印完毕后关闭锁。但是有可能还未关闭锁,线程就彻底退出了。倒是锁没有被释放。这种情况下,另外两个线程也运行到log_info时,就会去获取锁,从而阻塞卡死。
解决方法
使用valgrind
工具进行分析。
valgrind --tool=helgrind ./media_server
当死锁放生时,使用ctrl+C
终止。打印如下,thread2在退出时,仍然持有锁。说明锁在线程彻底退出时没有被释放。
解决方法参考。
发送通知来退出线程:
uint8_t exit_thread1 = false;
uint8_t exit_thread2 = false;
uint8_t exit_thread3 = false;
pthread_t tid1, tid2, tid3;
extern void *log_test_thread_handler(void *arg);
pthread_create(&tid1, NULL, log_test_thread_handler, "xxxxxxx");
pthread_create(&tid2, NULL, log_test_thread_handler, "OOOOOOO");
pthread_create(&tid3, NULL, log_test_thread_handler, "MMMMMMM");
sleep(1);
log_set_call(false);
sleep(1);
// log_trace("pthread 1 cancel: %d", pthread_cancel(tid1));
exit_thread1 = true;
pthread_join(tid1, NULL);
// log_trace("pthread 2 cancel: %d", pthread_cancel(tid2));
exit_thread2 = true;
// log_trace("pthread 3 cancel: %d", pthread_cancel(tid3));
exit_thread3 = true;
pthread_join(tid2, NULL);
pthread_join(tid3, NULL);
}
void log_test_thread_exit_handler(void *arg){
log_debug("%s", __FUNCTION__);
}
void *log_test_thread_handler(void *arg)
{
pthread_cleanup_push(log_test_thread_exit_handler, NULL);
char *buf = (char *)arg;
while (1) {
// usleep(400);
// pthread_testcancel();
if (exit_thread1) {
if (strcmp(buf, "xxxxxxx") == 0) {
pthread_exit(0);
}
}
if (exit_thread2) {
if (strcmp(buf, "OOOOOOO") == 0) {
pthread_exit(0);
}
}
if (exit_thread3) {
if (strcmp(buf, "MMMMMMM") == 0) {
pthread_exit(0);
}
}
// log_info called will be cause deadlock when call thread_cancel.
log_info("hello world! %s", buf);
}
pthread_cleanup_pop(1);
}
测试,能够正常退出而不会阻塞卡死。