记录线程退出(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);
}

测试,能够正常退出而不会阻塞卡死。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值