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 ;
}

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

由此改成了下面的代码:

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 ;
}

使用了一个 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);
}
...

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的函数// 如果没有收到信号,说明有数据没有被发送,那么就阻塞并且等待notifypthread_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 ;
}
...

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

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

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

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 ;
}

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/742734.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

离散化算法,以Acwing802.区间和为例子(C++实现)

目录 1.例题2.算法实现思路3.代码 1.例题 假定有一个无限长的数轴&#xff0c;数轴上每个坐标上的数都是 0现在&#xff0c;我们首先进行 n 次操作&#xff0c;每次操作将某一位置 x 上的数加 c接下来&#xff0c;进行 m 次询问&#xff0c;每个询问包含两个整数 l 和 r&#…

从0到1:如何用AI完成高质量的科研论文写作?

人工智能革命&#xff1a;如何让聊天机器人更懂你 人工智能正在以其强大的数据处理和语言生成能力改变世界。在学术界&#xff0c;大语言模型&#xff08;LLM&#xff09;为科学交流带来了一种新的工具。我们旨在有效地将AI工具与学术写作相结合&#xff0c;以更有效和更有影响…

在Linux服务器上安装Node.js

在Linux服务器上安装Node.js是一个相对简单的过程&#xff0c;以下是一份详细的安装指南&#xff0c;帮助您顺利在Linux服务器上安装Node.js。 一、准备工作 在开始安装Node.js之前&#xff0c;请确保您的Linux服务器已经连接到互联网&#xff0c;并且具有管理员权限。此外&a…

go语言tcp协议实现文件上传

一、客户端实现方案&#xff1a; package mainimport ("fmt""io""net""os" )func sendFile(filePath string, conn net.Conn) {defer conn.Close()// 获取文件名fileInfo, err : os.Stat(filePath)if err ! nil {fmt.Println("E…

【零基础学习04】嵌入式linux驱动中信号量功能基本实现

大家好,为了进一步提升大家对实验的认识程度,每个控制实验将加入详细控制思路与流程,欢迎交流学习。 今天给大家分享一下,linux系统里面信号量操作的具体实现,操作硬件为I.MX6ULL开发板。 第一:信号量基本简介 信号量是同步的一种方式,linux内核也提供了信号量…

在HTML5表单验证,自定义规则,用checkValidity()结合setCustomValidity()实现

自定义校验规则: 在HTML5表单验证API中,自定义规则通常通过监听表单元素的事件(如 input、change 或 invalid)并在JavaScript中进行验证来实现。以下是一个简单的例子,展示了如何创建一个自定义的验证规则,该规则要求输入字段必须包含至少两个连续的大写字母: <!DO…

数据大屏--->前端实时更新数据的几种方式

参考文章:实时数据更新 一、轮询:简单来说轮询就是客户端不停地调用服务端接口以获得最新的数据 1、短轮询:每隔3s向服务器发送一次请求- - -请求数据 优点:最大的优点就是实现简单 缺点:(1)无用的请求多,客户端不知道服务端什么时候数据更新,只能不停的向服务端发送请求, …

HTML5+CSS3+移动web——CSS 文字控制属性

系列文章目录 HTML5CSS3移动web——HTML 基础-CSDN博客https://blog.csdn.net/ymxk2876721452/article/details/136070953?spm1001.2014.3001.5502HTML5CSS3移动web——列表、表格、表单-CSDN博客https://blog.csdn.net/ymxk2876721452/article/details/136221443?spm1001.2…

java八股文复习-----2024/03/05----基础---跨域,过滤器,拦截器的区别

来源一 大彬八股文 来源二 2023 20W字八股文 2024秋招八股文 1.什么是跨域&#xff1f; 简单来讲&#xff0c;跨域是指从一个域名的网页去请求另一个域名的资源。由于有同源策略的关系&#xff0c;一般是不允许这么直接访问的。但是&#xff0c;很多场景经常会有跨域访问的…

反无人机电子护栏:原理、算法及简单实现

随着无人机技术的快速发展&#xff0c;其在航拍、农业、物流等领域的应用日益广泛。然而&#xff0c;无人机的不规范使用也带来了安全隐患&#xff0c;如侵犯隐私、干扰航空秩序等。为了有效管理无人机&#xff0c;反无人机电子护栏技术应运而生。 目录 一、反无人机电子护栏…

读取pkl文件,并得到它的所有key列表

import pickledef get_keys_from_pkl(file_path):with open(file_path, rb) as f:data pickle.load(f)keys list(data.keys())return keys# 用你的文件路径替换 your_file.pkl file_path your_file.pkl keys_list get_keys_from_pkl(file_path) print(keys_list)这段代码首…

怎样开始用selenium进行自动化测试?

在当今的软件开发过程中&#xff0c;自动化测试已成为确保应用程序质量和效率的关键工具。其中&#xff0c;Selenium作为一个强大的自动化测试工具&#xff0c;已经被广泛应用于Web应用程序的自动化测试。 但初学者要如何使用Selenium进行自动化测试&#xff1f;这当中包括Sel…

JavaScript---VConsole插件配置使用,一步到位简单实用!

1. 寻找到自己需要的VConsole插件js文件 个人喜欢BootCDN这个平台&#xff08;直接在线引用或者下载本地引入均可~&#xff09; vConsole (v3.15.1) - A lightweight, extendable front-end developer tool for mobile web page. | BootCDN - Bootstrap 中文网开源项目免费 C…

【Web】浅聊XStream反序列化本源之恶意动态代理注入

目录 简介 原理 复现 具体分析之前 我们反序列化了个什么&#xff1f; XStream反序列化的朴素通识 具体分析 第一步&#xff1a;unmarshal解组 第二步&#xff1a;readClassType获取动态代理类的Class对象 第三步&#xff1a;调用convertAnother对动态代理类进行实例…

JS数组相关知识

获取数组的最大值/最小值&#xff1a; let arrary [2,5,4] let max arrary[0] for(let i 0;i<arrary.length;i){if(arrary[i]>max){max arrary[i]} }console.log(max);//查询数组最小值let arr [2,21,34,23,45] let min arr[0] for(let i 0;i<arr.length;i){if…

mybatis-编写mapper.xml SQL语句时无提示

你们好&#xff0c;我是金金金。 场景 可以看见sql颜色都是白色的&#xff0c;而且编写的时候没有提示&#xff0c;简直痛苦 排查 中途有设置过SQL方言等&#xff0c;都没有解决我的问题 解决 很简单&#xff0c;https 改成 http 就有提示了&#xff01;&#xff01;&#x…

【Java并发知识总结 | 第二篇】乐观锁和悲观锁详讲

文章目录 2.乐观锁和悲观锁详讲2.1悲观锁2.2乐观锁2.3如何实现乐观锁2.3.1版本号机制2.3.2CAS算法2.3.3CAS底层 2.4乐观锁存在的问题2.4.1ABA问题&#xff08;1&#xff09;问题描述&#xff08;2&#xff09;解决 2.4.2循环时间长、开销大2.4.3只能保证一个共享变量的原子操作…

rust学习(手动写一个线程池)

哈哈&#xff0c;主要是为了练习一下rust的语法&#xff0c;不喜勿喷。 一.Executor申明 struct AExecutor<T> {results:Arc<Mutex<HashMap<u32,T>>>, //1functions:Arc<Mutex<Vec<ATask<T>>>> //2 } 1.results&#xff1a…

使用python实现一个dicom影像解析入库程序demo

简介 DICOM&#xff08;Digital Imaging and Communications in Medicine&#xff09;是医学图像和相关信息的国际标准。它定义了医学影像的格式和通信协议&#xff0c;使得不同设备和系统之间可以交换和共享医学图像和相关数据&#xff0c;如CT扫描、MRI图像、超声波图像等。…

代码随想录算法训练营第八天|344.反转字符串,541.反转字符串II、54.替换数字、151.翻转字符串里面的单词、55.右旋转字符串

代码随想录算法训练营第八天|344.反转字符串&#xff0c;541.反转字符串II、54.替换数字、151.翻转字符串里面的单词、55.右旋转字符串 344.反转字符串 编写一个函数&#xff0c;其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。 不要给另外的数组分配…