当前位置: 首页 > news >正文

C++:错误代码分析<2>

🌏主页:R6bandito_

🚀所属专栏:C/C++错误代码收集整理

源码

考虑以下代码:

void do_some_work() {std::cout << "Do some work" << std::endl;
}int main(int argc, const char* argv[]) {std::vector<std::thread> threads;for (int i = 0; i < 10; i++) {threads.emplace_back(std::thread(do_some_work));}for (auto& th : threads) {th.join();}return 0;
}

问题描述

        根据代码,理想输出应是打印10行字符串,每行的字符串都是:"Do some work"。但是实际输出却出现了严重的格式问题。且每次的输出格式都会发生一定变化。部分示例错误输出如下:

/*第一次*/
Do some workDo some work
Do some work
Do some work
Do some work
Do some work
Do some work
Do some work
Do some workDo some work/*第二次*/
Do some workDo some workDo some workDo some workDo some work
Do some workDo some work
Do some workDo some workDo some work

原因分析

        该段代码定义了一个动态存储线程(thread)的vector容器,并向其中压入了10个线程任务,每个线程任务都是执行do_some_work。待线程全部启动后通过基于范围的for循环遍历该vector容器,将所有线程的运行结果通过join进行回收。

        而问题正是出在线程上,我们成功启动了10个线程,但是却忽略了一点:线程的执行顺序并不确定,取决于操作系统的调度策略线程之间是并发运行的,而并非依序执行。

        当每次运行程序时,操作系统都会根据自身的调度策略,调整线程的执行顺序,因而会造成每次执行程序时,看到的输出结果都不相同。

举个可能的例子:

线程A拿到了CPU所分给的执行时间切片,当线程A执行到打印"Do some work"时,还没来得及打印换行符,便被操作系统强制暂停。

这时,论到线程B执行了,线程B成功执行了整套流程。一段时间后,A又重新拿到时间切片开始执行,并将剩下的一个换行符打印。

因此,当最后调用join回收资源的时候,线程AB的结果就如下所示:

Do some workDo some work  //线程A的换行符被打印到别处去了

代码修正

        解决方法:确保每次只有一个线程能够访问并执行cout,因此需要用到互斥锁

        当该线程获得锁后,其它线程由于无法获得锁,因此就算分得时间切片也无法执行。这样就确保了每次只有得到锁的那个线程能够被完整执行。

修正代码如下:

std::mutex mtx;void do_some_work() {std::lock_guard<std::mutex> lock(mtx);std::cout << "Do some work" << std::endl;
}int main(int argc, const char* argv[]) {std::vector<std::thread> threads;for (int i = 0; i < 10; i++) {threads.emplace_back(std::thread(do_some_work));}for (auto& th : threads) {th.join();}return 0;
}

输出结果:

Do some work
Do some work
Do some work
Do some work
Do some work
Do some work
Do some work
Do some work
Do some work
Do some work

🌹🌹🌹

http://www.lryc.cn/news/459684.html

相关文章:

  • 怎么ping网络ip地址通不通
  • 前端新机部署
  • 对比 Babel、SWC 和 Oxc:JavaScript 和 TypeScript 工具的未来
  • MySQL SELECT 查询(三):查询常用函数大全
  • axios 的 get 请求传参数
  • 用C++编写信息管理系统(歌单信息管理)
  • 对层级聚类树进行模块分割,定位基因在哪个模块中
  • 机器学习【金融风险与风口评估及其应用】
  • 【计算机网络 - 基础问题】每日 3 题(三十八)
  • 深入浅出MongoDB(五)
  • 【conda】创建、激活、删除虚拟环境
  • 关于int*的*号归属权问题
  • leetcode---素数,最小质因子,最大公约数
  • 基于stm32的蓝牙模块实验
  • C语言解决TopK问题
  • 磁盘存储链式结构——B树与B+树
  • 如何批量从sql语句中提取表名
  • 怎么把音频的速度调慢?6个方法调节音频速度
  • K8s-services+pod详解1
  • 从RNN讲起(RNN、LSTM、GRU、BiGRU)——序列数据处理网络
  • python:假的身份信息生成模块faker
  • spring task的使用场景
  • 美畅物联丨剖析 GB/T 28181 与 GB 35114:视频汇聚领域的关键协议
  • uni-app 开发的应用快速构建成鸿蒙原生应用
  • 代码随想录算法训练营| 669. 修剪二叉搜索树 、 108.将有序数组转换为二叉搜索树 、 538.把二叉搜索树转换为累加树
  • Django模型实现外键自关联
  • Android ViewModel
  • 优先算法1--双指针
  • 利用弹性盒子完成移动端布局(第二次实验作业)
  • C# 字符串(string)三个不同的处理方法:IsNullOrEmpty、IsInterned 、IsNullOrWhiteSpace