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

[c++] 记录一次引用使用不当导致的 bug

在工作中看到了如下代码,代码基于 std::thread 封装了一个 Thread 类。Thread 封装了业务开发中常用的接口,比如设置调度策略,设置优先级,设置线程名。如下代码删去了不必要的代码,只保留能说明问题的代码。从代码实现上来看,我们看不出什么问题,创建一个线程,第一个形参是线程的入口函数,后边的传参是线程入口函数的参数列表。

class Thread {
public:template <class Function, class... Args>Thread(Function &&f, Args &&...args) noexcept: internal_{[func = std::forward<Function>(f), &args...]() {func(args...);}}{}private:std::thread internal_;
};

Thread 类在大部分使用场景下是没问题的,比如下面的使用方式,创建了一个线程,线程中是一个死循环,每隔一秒打印一次 "thread running",可以正常工作。

#include <stdio.h>
#include <unistd.h>
#include <memory>
#include <thread>
#include <vector>class Thread {
public:template <class Function, class... Args>Thread(Function &&f, Args &&...args) noexcept: internal_{[func = std::forward<Function>(f), args...]() {func(args...);}}{}private:std::thread internal_;
};void func() {while (1) {printf("thread running\n");sleep(1);}
}int main() {Thread *t = new Thread(func);sleep(100);return 0;
}

1 问题现象

在下边这个使用场景下,就能暴露出来 Thread 的问题。

如下代码中连续创建了 8 个线程,线程的入口函数是 func(),func() 的形参是 Obj 对象,Obj 中的成员 i_  分别取值 0 ~ 7。

#include <stdio.h>
#include <unistd.h>
#include <iostream>
#include <memory>
#include <thread>
#include <vector>class Thread {
public:template <class Function, class... Args>Thread(Function &&f, Args &&...args) noexcept: internal_{[func = std::forward<Function>(f), &args...]() {func(args...);}}{}private:std::thread internal_;
};class Obj {
public:Obj(int i) {i_ = i;std::cout << "Obj(), i: " << i_ << std::endl;}Obj(const Obj &obj) {i_ = obj.i_;std::cout << "copy constructor, i: " << i_ << std::endl;}~Obj() {std::cout << "~Obj(), i: " << i_ << std::endl;}int i_;};void func(Obj obj) {printf("                in thread, i: %d\n", obj.i_);
}int main() {std::vector<Thread *> threads;int i = 0;for (i = 0; i < 8; i++) {printf("    out thread, i: %d\n", i);Obj obj(i);auto tmp = new Thread(func, obj);printf("after create thread %d\n", i);threads.emplace_back(tmp);// sleep(2);}sleep(100);return 0;
}

上边的代码编译之后,运行结果如下所示。我们的预期是在 func() 中的打印分别是 0 ~ 7,每个数字打印一次。但实际的打印结果是有重复的,如下图所示,2 有重复的,7 也有重复的。

root@wangyanlong-virtual-machine:/home/wyl/cpp# ./a.outout thread, i: 0
Obj(), i: 0
after create thread 0
~Obj(), i: 0out thread, i: 1
Obj(), i: 1
after create thread 1
~Obj(), i: 1out thread, i: 2
Obj(), i: 2
copy constructor, i: 2in thread, i: 2
~Obj(), i: 2
copy constructor, i: 2in thread, i: 2
~Obj(), i: 2
after create thread 2
~Obj(), i: 2out thread, i: 3
Obj(), i: 3
copy constructor, i: 2in thread, i: 2
~Obj(), i: 2
copy constructor, i: 3in thread, i: 3
~Obj(), i: 3
after create thread 3
~Obj(), i: 3out thread, i: 4
Obj(), i: 4
after create thread 4
~Obj(), i: 4out thread, i: 5
Obj(), i: 5
copy constructor, i: 4
after create thread 5
~Obj(), i: 5out thread, i: 6
Obj(), i: 6in thread, i: 4
~Obj(), i: 4
copy constructor, i: 5in thread, i: 5
~Obj(), i: 5
after create thread 6
~Obj(), i: 6out thread, i: 7
Obj(), i: 7
copy constructor, i: 7in thread, i: 7
~Obj(), i: 7
after create thread 7
~Obj(), i: 7
copy constructor, i: 7in thread, i: 7
~Obj(), i: 7

上边的代码把 sleep(2) 注释打开,打印结果是符合预期的。

或者将 main() 中的 Thread() 改成 std::thread,打印结果也是符合预期的,说明这种使用方式是符合 c++ 规范的。

2 问题分析

导致问题的原因有以下几个方面:

(1)线程的构造函数入参是右值引用,这个右值引用的生命周期在构造函数返回的时候已经结束了。右值引用,指向一个临时的存储空间,在反复创建 8 个线程期间,8 个右值引用指向的是同一块内存空间,后边的值会将前边的值覆盖。

(2)线程构造函数中,std::thread 的回调函数是一个 lambda 表达式,lambda 表达式中引用捕获了 args。

(3)在 Thread 构造函数中创建了线程,但是线程并不是立即执行的,从创建到真正执行是有一段时间的延迟。这样当线程真正运行的时候,再从 args 引用里边读取数据,取出来的是这块内存最新的数据,属于这个线程的数据已经被覆盖。

3 问题修改

引用捕获改成值捕获

如下代码,在 Thread() 构造函数中的 lambda 表达式对 args 的引用捕获改成值捕获。

#include <stdio.h>
#include <unistd.h>
#include <iostream>
#include <memory>
#include <thread>
#include <vector>class Thread {
public:template <class Function, class... Args>Thread(Function &&f, Args &&...args) noexcept: internal_{[func = std::forward<Function>(f), args...]() {func(args...);}}{}private:std::thread internal_;
};class Obj {
public:Obj(int i) {i_ = i;std::cout << "Obj(), i: " << i_ << std::endl;}Obj(const Obj &obj) {i_ = obj.i_;std::cout << "copy constructor, i: " << i_ << std::endl;}~Obj() {std::cout << "~Obj(), i: " << i_ << std::endl;}int i_;};void func(Obj obj) {printf("                in thread, i: %d\n", obj.i_);
}int main() {std::vector<Thread *> threads;int i = 0;for (i = 0; i < 8; i++) {printf("    out thread, i: %d\n", i);Obj obj(i);auto tmp = new Thread(func, obj);printf("after create thread %d\n", i);threads.emplace_back(tmp);// sleep(2);}sleep(100);return 0;
}

相关文章:

[c++] 记录一次引用使用不当导致的 bug

在工作中看到了如下代码&#xff0c;代码基于 std::thread 封装了一个 Thread 类。Thread 封装了业务开发中常用的接口&#xff0c;比如设置调度策略&#xff0c;设置优先级&#xff0c;设置线程名。如下代码删去了不必要的代码&#xff0c;只保留能说明问题的代码。从代码实现…...

能不能节约百分之九十的算力来训练模型

Sora是由OpenAI开发的视频生成模型&#xff0c;它采用了多种先进的技术和架构&#xff0c;能够根据文本描述生成长达一分钟的高清视频。虽然OpenAI并未公开Sora的详细模型架构和实现细节&#xff0c;但我们可以根据公开的信息和参考论文来了解其技术架构。 Sora的核心技术架构主…...

LeetCode206: 反转链表.

题目描述 给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表。 示例 解题方法 假设链表为 1→2→3→∅&#xff0c;我们想要把它改成∅←1←2←3。在遍历链表时&#xff0c;将当前节点的 next指针改为指向前一个节点。由于节点没有引用其前一…...

高级统计方法 第1次作业

概念 1. 请解释什么是P值&#xff0c;怎么计算p值&#xff0c;p值结果怎么理解&#xff0c;p值有哪些应用......&#xff1f; &#xff08;a&#xff09;什么是P值 P值是一种用来判定假设检验结果的一个参数&#xff0c;它描述了在原假设为真的情况下&#xff0c;比所得到的…...

spinalhdl,vivado,fpga

https://spinalhdl.github.io/SpinalDoc-RTD/master spinal hdl sudo apt install openjdk-17-jdk scala curl echo “deb https://repo.scala-sbt.org/scalasbt/debian all main” | sudo tee /etc/apt/sources.list.d/sbt.list echo “deb https://repo.scala-sbt.org/scal…...

Tomcat线程池原理(下篇:工作原理)

文章目录 前言正文一、执行线程的基本流程1.1 JUC中的线程池执行线程1.2 Tomcat 中线程池执行线程 二、被改造的阻塞队列2.1 TaskQueue的 offer(...)2.2 TaskQueue的 force(...) 三、总结 前言 Tomcat 线程池&#xff0c;是依据 JUC 中的线程池 ThreadPoolExecutor 重新自定义…...

【服务器数据恢复】通过reed-solomon算法恢复raid6数据的案例

服务器数据恢复环境&#xff1a; 一台网站服务器中有一组由6块磁盘组建的RAID6磁盘阵列&#xff0c;操作系统层面运行MySQL数据库和存放一些其他类型文件。 服务器故障&#xff1a; 该服务器在工作过程中&#xff0c;raid6磁盘阵列中有两块磁盘先后离线&#xff0c;不知道是管理…...

LeetCode 2583.二叉树中的第 K 大层和:层序遍历 + 排序

【LetMeFly】2583.二叉树中的第 K 大层和&#xff1a;层序遍历 排序 力扣题目链接&#xff1a;https://leetcode.cn/problems/kth-largest-sum-in-a-binary-tree/ 给你一棵二叉树的根节点 root 和一个正整数 k 。 树中的 层和 是指 同一层 上节点值的总和。 返回树中第 k …...

element ui 安装 简易过程 已解决

我之所以将Element归类为Vue.js&#xff0c;其主要原因是Element是&#xff08;饿了么团队&#xff09;基于MVVM框架Vue开源出来的一套前端ui组件。我最爱的就是它的布局容器&#xff01;&#xff01;&#xff01; 下面进入正题&#xff1a; 1、Element的安装 首先你需要创建…...

websoket

WebSockets 是一种先进的技术。它可以在用户的浏览器和服务器之间打开交互式通信会话。你可以向服务器发送消息并接收事件驱动的响应&#xff0c;而无需通过轮询服务器的方式以获得响应&#xff0c;比较典型的应用场景就是即时通讯&#xff08;聊天&#xff09;系统。 <!DOC…...

案例:微服务从Java/SpringBoot迁移到Golan

基于 Java 的微服务&#xff0c;特别是那些使用 Spring Boot 的微服务&#xff0c;长期以来因其强大的功能和广泛的社区支持而闻名。Spring Boot 的约定优于配置方法简化了微服务的部署和开发&#xff0c;提供了大量开箱即用的功能&#xff0c;例如自动配置、独立功能和简单的依…...

小波变换模拟

小波变换是一种信号处理技术&#xff0c;通过在时间-频率域中使用基于小波的函数进行信号分析。小波变换在处理非平稳信号和图像时特别有用&#xff0c;可以将信号分解为不同频率的成分。它在数据压缩、去噪、特征提取等领域有广泛应用。 MATLAB中提供了用于二维离散小波变换的…...

cv::Mat图像操作

图像读写 //include header #include <opencv2/imgcodecs.hpp>/** Currently, the following file formats are supported: Windows bitmaps - *.bmp, *.dib (always supported) JPEG files - *.jpeg, *.jpg, *.jpe (see the Note section) JPEG 2000 files - *.jp2 (s…...

【机器学习基础】一元线性回归(适合初学者的保姆级文章)

&#x1f680;个人主页&#xff1a;为梦而生~ 关注我一起学习吧&#xff01; &#x1f4a1;专栏&#xff1a;机器学习 欢迎订阅&#xff01;后面的内容会越来越有意思~ &#x1f4a1;往期推荐&#xff1a; 【机器学习基础】机器学习入门&#xff08;1&#xff09; 【机器学习基…...

2024年软件测试岗位-面试

第一部分&#xff1a; 1、自我介绍&#xff1a;简历写到的快速描述&#xff0c;学校、学历、工作经验等&#xff08;注意&#xff1a;不要过度优化简历&#xff0c;你不写别人可能会问&#xff0c;但你写了别人一定会问&#xff01;&#xff09; 第二部分&#xff1a; 1、功能测…...

【坑】Spring Boot整合MyBatis,一级缓存失效

一、Spring Boot整合MyBatis&#xff0c;一级缓存失效 1.1、概述 MyBatis一级缓存的作用域是同一个SqlSession&#xff0c;在同一个SqlSession中执行两次相同的查询&#xff0c;第一次执行完毕后&#xff0c;Mybatis会将查询到的数据缓存起来&#xff08;缓存到内存中&#xf…...

J7 - 对于ResNeXt-50算法的思考

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 | 接辅导、项目定制 J6周有一段代码如下 思考过程 首先看到这个问题的描述&#xff0c;想到的是可能使用了向量操作的广播机制然后就想想办法验证一下&…...

R3F(React Three Fiber)基础篇

之前一直在做ThreeJS方向&#xff0c;整理了两篇R3F&#xff08;React Three Fiber&#xff09;的文档&#xff0c;这是基础篇&#xff0c;如果您的业务场景需要使用R3F&#xff0c;您又对R3F不太了解&#xff0c;或者不想使用R3F全英文文档&#xff0c;您可以参考一下这篇&…...

torch\tensorflow在大语言模型LLM中的作用

文章目录 torch\tensorflow在大语言模型LLM中的作用 torch\tensorflow在大语言模型LLM中的作用 在大型语言模型&#xff08;LLM&#xff09;中&#xff0c;PyTorch和TensorFlow这两个深度学习框架起着至关重要的作用。它们为构建、训练和部署LLM提供了必要的工具和基础设施。 …...

设计模式-创建型模式-单例模式

0 引言 创建型模式&#xff08;Creational Pattern&#xff09;关注对象的创建过程&#xff0c;是一类最常用的设计模式&#xff0c;每个创建型模式都通过采用不同的解决方案来回答3个问题&#xff1a;创建什么&#xff08;What&#xff09;&#xff0c;由谁创建&#xff08;W…...

云原生核心技术 (7/12): K8s 核心概念白话解读(上):Pod 和 Deployment 究竟是什么?

大家好&#xff0c;欢迎来到《云原生核心技术》系列的第七篇&#xff01; 在上一篇&#xff0c;我们成功地使用 Minikube 或 kind 在自己的电脑上搭建起了一个迷你但功能完备的 Kubernetes 集群。现在&#xff0c;我们就像一个拥有了一块崭新数字土地的农场主&#xff0c;是时…...

css实现圆环展示百分比,根据值动态展示所占比例

代码如下 <view class""><view class"circle-chart"><view v-if"!!num" class"pie-item" :style"{background: conic-gradient(var(--one-color) 0%,#E9E6F1 ${num}%),}"></view><view v-else …...

PHP和Node.js哪个更爽?

先说结论&#xff0c;rust完胜。 php&#xff1a;laravel&#xff0c;swoole&#xff0c;webman&#xff0c;最开始在苏宁的时候写了几年php&#xff0c;当时觉得php真的是世界上最好的语言&#xff0c;因为当初活在舒适圈里&#xff0c;不愿意跳出来&#xff0c;就好比当初活在…...

Admin.Net中的消息通信SignalR解释

定义集线器接口 IOnlineUserHub public interface IOnlineUserHub {/// 在线用户列表Task OnlineUserList(OnlineUserList context);/// 强制下线Task ForceOffline(object context);/// 发布站内消息Task PublicNotice(SysNotice context);/// 接收消息Task ReceiveMessage(…...

从深圳崛起的“机器之眼”:赴港乐动机器人的万亿赛道赶考路

进入2025年以来&#xff0c;尽管围绕人形机器人、具身智能等机器人赛道的质疑声不断&#xff0c;但全球市场热度依然高涨&#xff0c;入局者持续增加。 以国内市场为例&#xff0c;天眼查专业版数据显示&#xff0c;截至5月底&#xff0c;我国现存在业、存续状态的机器人相关企…...

dedecms 织梦自定义表单留言增加ajax验证码功能

增加ajax功能模块&#xff0c;用户不点击提交按钮&#xff0c;只要输入框失去焦点&#xff0c;就会提前提示验证码是否正确。 一&#xff0c;模板上增加验证码 <input name"vdcode"id"vdcode" placeholder"请输入验证码" type"text&quo…...

转转集团旗下首家二手多品类循环仓店“超级转转”开业

6月9日&#xff0c;国内领先的循环经济企业转转集团旗下首家二手多品类循环仓店“超级转转”正式开业。 转转集团创始人兼CEO黄炜、转转循环时尚发起人朱珠、转转集团COO兼红布林CEO胡伟琨、王府井集团副总裁祝捷等出席了开业剪彩仪式。 据「TMT星球」了解&#xff0c;“超级…...

vue3 定时器-定义全局方法 vue+ts

1.创建ts文件 路径&#xff1a;src/utils/timer.ts 完整代码&#xff1a; import { onUnmounted } from vuetype TimerCallback (...args: any[]) > voidexport function useGlobalTimer() {const timers: Map<number, NodeJS.Timeout> new Map()// 创建定时器con…...

大模型多显卡多服务器并行计算方法与实践指南

一、分布式训练概述 大规模语言模型的训练通常需要分布式计算技术,以解决单机资源不足的问题。分布式训练主要分为两种模式: 数据并行:将数据分片到不同设备,每个设备拥有完整的模型副本 模型并行:将模型分割到不同设备,每个设备处理部分模型计算 现代大模型训练通常结合…...

第 86 场周赛:矩阵中的幻方、钥匙和房间、将数组拆分成斐波那契序列、猜猜这个单词

Q1、[中等] 矩阵中的幻方 1、题目描述 3 x 3 的幻方是一个填充有 从 1 到 9 的不同数字的 3 x 3 矩阵&#xff0c;其中每行&#xff0c;每列以及两条对角线上的各数之和都相等。 给定一个由整数组成的row x col 的 grid&#xff0c;其中有多少个 3 3 的 “幻方” 子矩阵&am…...