• C后端开发,记录一个关于条件变量的死锁bug


    过程

    前几天使用IPC进程通信的原理写了一个聊天室,并且支持高并发。

    在对于预防共享内存被多个进程同时使用导致信息丢失的情况时,使用了互斥锁,在客户端的代码中,最一开始是这样的:

    void send_by_signal() {
        DBG("DBG: send_by_signal...\n");
        char buff[MAX_NAME_LENGTH] = {0};
        while(1) {
            int ret = scanf("%[^\n]", buff);
            getchar();
            if (ret == 0) continue;
            // 上锁
            pthread_mutex_lock(&share_memory->mutex);
            strcpy(share_memory->name, name);
            strcpy(share_memory->message, buff);
            pthread_mutex_unlock(&share_memory->mutex);
            // 解锁
            kill(server_pid, SIGUSR1);
            DBG(GREEN"%s"NONE"->"YELLOW"%s\n"NONE, share_memory->name, share_memory->message);
        }
    
        return ;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    但是在高并发测试之后发现,上面的代码具有这样的问题。当解锁后,下一个拿到共享内存的不一定是服务端,有可能被其他的客户端拿到,然后对其中的数据造成覆盖,从而数据丢失,因此为了解决这个问题,需要对共享内存中的数据是否被“消耗”而做出判断。

    由此改成了下面的代码:

    void send_by_signal() {
        DBG("DBG: send_by_signal...\n");
        char buff[MAX_NAME_LENGTH] = {0};
        while(1) {
            int ret = scanf("%[^\n]", buff);
            getchar();
            if (ret == 0) continue;
            int lock_before_strcpy_flag;
            do {
                pthread_mutex_lock(&share_memory->mutex);
                lock_before_strcpy_flag = 0;
                // 如果有数据
                if (strlen(share_memory->message)) {
    		            // 解锁之后
                    pthread_mutex_unlock(&share_memory->mutex);
                    lock_before_strcpy_flag = 1;
                }
            } while(lock_before_strcpy_flag);
            strcpy(share_memory->name, name);
            strcpy(share_memory->message, buff);
            pthread_mutex_unlock(&share_memory->mutex);
            kill(server_pid, SIGUSR1);
            DBG(GREEN"%s"NONE"->"YELLOW"%s\n"NONE, share_memory->name, share_memory->message);
        }
        return ;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    使用了一个 do-while 循环,当客户端拿到共享内存之后,会对其中的数据进行检测,如果存在数据,说明数据还未被消耗,这个时候不能写数据,因此需要再解锁继续等待,基本算是解决了高并发的问题。

    之后学习了condvar,发现只要从服务端发出可以输入消息的notify,通知客户端进程,就可以解决“未消耗的问题”,因此写出了以下代码:

    server :

    ...
    void print(int signum) {
        DBG("Received a signal...");
        pthread_mutex_lock(&share_memory->mutex);
        if (strlen(share_memory->message) == 0) {
            pthread_mutex_unlock(&share_memory->mutex);
            return ;
        }
        printf("<%s> : %s\n", share_memory->name, share_memory->message);
        memset(share_memory->message, 0, MAX_MSG);
        pthread_mutex_unlock(&share_memory->mutex);
        // 在这里添加了发送条件变量的函数,表示数据已经被发送出去了,可以接收新数据了
        pthread_cond_signal(&share_memory->cond);
    }
    ...
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    client :

    ...
    void send_by_signal() {
        DBG("DBG: send_by_signal...\n");
        char buff[MAX_NAME_LENGTH] = {0};
        while(1) {
            int ret = scanf("%[^\n]", buff);
            getchar();
            if (ret == 0) continue;
            pthread_mutex_lock(&share_memory->mutex);
            // 在这里添加了等待condvar的函数
            // 如果没有收到信号,说明有数据没有被发送,那么就阻塞并且等待notify
            pthread_cond_wait(&share_memory->cond, &share_memory->mutex);
            strcpy(share_memory->name, name);
            strcpy(share_memory->message, buff);
            pthread_mutex_unlock(&share_memory->mutex);
            kill(server_pid, SIGUSR1);
            DBG(GREEN"%s"NONE"->"YELLOW"%s\n"NONE, share_memory->name, share_memory->message);
        }
        return ;
    }
    ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    但是运行之后发生了死锁,经过排查之后,发现因为我的 print 函数如果想要触发,是需要收到notify的,但是在 client 端,程序被阻塞到了 pthread_cond_wait 这里,因此造成了死锁。

    解决方案就是,在这个函数上套上一个 if 语句:

    if (strlen(share_memory->message) != 0) {
    		pthread_cond_wait(&share_memory->cond, &share_memory->mutex);
    }
    
    • 1
    • 2
    • 3

    client 拿到共享内存后,首先检查共享内存中是否有数据,如果有,说明有数据未被读出,就进入等待notify的状态,如果没有,说明可以存入数据,直接跳过等待函数,输入数据。这个时候其他的 client 都会检测到数据存在,因此都会进入等待状态,这个时候 server 端收到信号执行 print 函数,之后输出条件信号。

    本以为能够解决问题,但是还是忽略了一点,当 server 端输出条件信号时,发现出现了虚假唤醒,为什么呢?

    原因是因为 if ,当一个notify出现时,所有的 client 端都会被这个notify唤醒,但是只有一个能够拿到condvar,因此会出现一个 client 已经输入数据,但是另一个 client 也跳出wait状态,覆写数据的情况,仍然会造成数据丢失。因此 if 是不够的,需要换成 while

    最终的代码:

    void send_by_signal() {
        DBG("DBG: send_by_signal...\n");
        char buff[MAX_NAME_LENGTH] = {0};
        while(1) {
            int ret = scanf("%[^\n]", buff);
            getchar();
            if (ret == 0) continue;
            pthread_mutex_lock(&share_memory->mutex);
            while (strlen(share_memory->message) != 0) {
                pthread_cond_wait(&share_memory->cond, &share_memory->mutex);
            }
            strcpy(share_memory->name, name);
            strcpy(share_memory->message, buff);
            pthread_mutex_unlock(&share_memory->mutex);
            kill(server_pid, SIGUSR1);
            DBG(GREEN"%s"NONE"->"YELLOW"%s\n"NONE, share_memory->name, share_memory->message);
        }
        return ;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    运行速度超级快,永远比人的手速要快,大功告成~~~~

  • 相关阅读:
    [发现了好东西] MS teams 使用-表情小窗口
    2022年4月最新面经答案总结(Java基础、数据库、JVM、计网、计操、集合、多线程、Spring)持续更新
    VMware虚拟机安装Windows 10
    VuePress搭建文档网站/个人博客(详细配置)主题配置-导航栏配置
    半量化交易(二)
    [附源码]Python计算机毕业设计SSM乐多多宠物店网站(程序+LW)
    图形界面四则运算计算器(Python+PyQt5)
    TEMU半托管模式引领跨境电商新风尚
    【PAT甲级 - C++题解】1088 Rational Arithmetic
    Word第一课
  • 原文地址:https://blog.csdn.net/weixin_74239689/article/details/136692953