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

Studying-多线程学习Part1-线程库的基本使用、线程函数中的数据未定义错误、互斥量解决多线程数据共享问题

来源:多线程编程


线程库的基本使用

两个概念:

  • 进程是运行中的程序
  • 线程是进程中的进程

串行运行:一次只能取得一个任务并执行这一个任务

并行运行:可以同时通过多进程/多线程的方式取得多个任务,并以多进程或多线程的方式同时执行这些任务。

线程的最大数量取决于cpu的核心数。

thread的函数原型:传入一个函数名就可以运行

1.创建线程:

使用thread函数,但是如下程序会报错,原因在于线程还在运行的时候,主程序可能就已经结束了。 

#include <iostream>
#include <thread>
using namespace std;void printHelloWorld() {cout << "Hello World" << endl;return;
}int main() {//1.创建线程thread thread1(printHelloWorld);return 0;
}

2.等待线程完成:

为了解决上述问题,我们可以使用join()函数,来让主线程等待线程执行完毕。

PS:join()函数是阻塞的,程序会一直停留在join()处,直到线程运行完毕

#include <iostream>
#include <thread>
using namespace std;void printHelloWorld() {cout << "Hello World" << endl;return;
}int main() {//1.创建线程thread thread1(printHelloWorld);//主程序等待线程执行完毕join()thread1.join();return 0;
}

3.传入参数:

如果函数带有参数,我们也可以在thread的后面增加参数列表。 

#include <iostream>
#include <thread>
#include <string>
using namespace std;void printHelloWorld(string msg, string msg2) {cout << msg << endl;cout << msg2 << endl;return;
}int main() {//1.创建线程thread thread1(printHelloWorld, "Hello Thread", "Hello World");//主程序等待线程执行完毕join()thread1.join();return 0;
}

4.分离线程:

我们有时候也希望主程序不需要等待线程完成,而是让线程它在后台运行,这时候我们可以使用到detach()函数分离线程。(下述情况什么都不会打印,来不及打印)

#include <iostream>
#include <thread>
#include <string>
using namespace std;void printHelloWorld(string msg, string msg2) {cout << msg << endl;cout << msg2 << endl;return;
}int main() {//1.创建线程thread thread1(printHelloWorld, "Hello Thread", "Hello World");//主程序等待线程执行完毕join()thread1.detach();return 0;
}

5. joinable():

该函数返回一个布尔值,如果线程可以被join()或detach(),则返回true,否则返回false。如果我们试图对一个不可加入的线程调用join()或detach(),则会抛出一个std::system_error异常。

#include <iostream>
#include <thread>
#include <string>
using namespace std;void printHelloWorld(string msg, string msg2) {cout << msg << endl;cout << msg2 << endl;return;
}int main() {//1.创建线程thread thread1(printHelloWorld, "Hello Thread", "Hello World");//主程序等待线程执行完毕join()bool isJoin = thread1.joinable();if (isJoin) {thread1.join();}return 0;
}

线程函数中的数据未定义错误

1.传递临时变量的问题:

#include <iostream>	
#include <thread>
using namespace std;void foo(int& x) {x += 1;
}int main() {thread t(foo, 1);t.join();return 0;
}

在上述例子中,我们将临时变量1作为参数传递给了foo, 这样会导致在线程函数执行时,临时变量`1`已经销毁,从而导致未定义行为。

解决方案是将变量复制到一个持久的对象中,然后将该对象传递给线程。例如,我们可以将`1`复制到一个`int`类型的变量中,然后将该变量的引用传递给线程。

#include <iostream>	
#include <thread>
using namespace std;void foo(int& x) {x += 1;
}int main() {int a = 1;//ref函数,将a转换为自身的引用,在线程函数中需要使用thread t(foo, ref(a));t.join();cout << a << endl;return 0;
}

2. 传递指针或引用指向局部变量的问题: 

#include <iostream>	
#include <thread>
using namespace std;thread t;
void foo(int& x) {x += 1;
}void test() {int a = 1;t = thread(foo, ref(a));return;
}int main() {test();t.join();return 0;
}

如果传入线程中的参数是局部变量,则线程在进行的时候,变量就已经被销毁了,无法得到结果。解决办法就是让a变量变为全局变量。或者join()放在thread()函数后面。关键就在于要注意变量的生命周期。

3. 传递指针或引用指向已释放的内存的问题: 

#include <iostream>
#include <thread>
using namespace std;void foo(int* x) {cout << *x << endl; // 访问已经被释放的内存
}
int main() {int* ptr = new int(1);thread t(foo, ptr); // 传递已经释放的内存delete ptr;t.join();return 0;
}

提前把ptr进行删除,会导致传入的是已经释放内存了的空间,结果变得不确定。这也是要注意变量的生命周期和作用范围的问题。

4. 类成员函数作为入口函数,类对象被提前释放

和上一个问题的原因类似,在创建线程之后,如果类对象已经被销毁,这会导致在线程执行时无法访问对象,可能会导致程序崩溃或者产生未定义的行为。

#include <iostream>
#include <thread>
using namespace std;class A {
public:void foo() {cout << "Hello" << endl;}
};int main() {A a;thread t(&A::foo, &a);/*进行一系列操作,可能会导致a被释放*/t.join();return 0;
}

为了解决上述问题,我们需要使用指针来保证地址有效,但如果用普通指针,我们需要自行进行指针的删除和释放。因此我们可以采用智能指针shared_ptr来管理类对象的生命周期,确保在线程执行期间对象不会被销毁。具体来说,可以在创建线程之前,将类对象的指针封装在shared_ptr 对象中,并将其作为参数传递给线程。这样,在线程执行期间,即使类对象的所有者释放了其所有权。

#include <iostream>
#include <thread>
#include <memory>
using namespace std;class A {
public:void foo() {cout << "Hello" << endl;}
};int main(){shared_ptr<A> a = make_shared<A>();thread t(&A::foo, a);/*进行一系列操作,可能会导致a被释放*/t.join();return 0;
}

5.入口函数为类的私有成员函数 

#include <iostream>
#include <thread>
using namespace std;class A {
private:void foo() {cout << "Hello" << endl;}
};int main() {shared_ptr<A> a = make_shared<A>();thread t(&A::foo, a); //报错不能调用foo函数/*进行一系列操作,可能会导致a被释放*/t.join();return 0;
}

如果函数是私有成员函数,那么是无法调用的。需要使用到友元函数, 并在函数中调用foo()函数。

#include <iostream>
#include <thread>
using namespace std;class A {
private:friend void thread_foo();void foo() {cout << "Hello" << endl;}
};void thread_foo() {shared_ptr<A> a = make_shared<A>();thread t(&A::foo, a); //报错不能调用foo函数/*进行一系列操作,可能会导致a被释放*/t.join();return;
}int main() {thread_foo();return 0;
}

互斥量解决多线程数据共享问题 

数据共享问题分析

在多个线程中共享数据时,需要注意线程安全问题。如果多个线程同时访问同一个变量,并且其中至少有一个线程对该变量进行了写操作,那么就会出现数据竞争问题。数据竞争可能会导致程序崩溃、产生未定义的结果,或者得到错误的结果。

数据竞争问题简单的可以理解为,t1和t2在取数据的时候,可能会取到同样的a的值,也就是t2不会等待t1完成对a的所有加1操作之后,才去取a,这样就会导致a无法正确的加200000次。

#include <iostream>
#include <thread>
using namespace std;
int share_data = 0;
void fun() {for (int i = 0; i < 100000; ++i) {share_data += 1;}
}int main() {thread t1(fun);thread t2(fun);t1.join();t2.join();cout << share_data << endl;return 0;
}

为了解决这个问题,我们需要保证当一个线程去拿a的值的时候,其他的线程不能去拿a,保证共享数据的安全。 为了避免数据竞争问题,需要使用同步机制来确保多个线程之间对共享数据的访问是安全的。常见的同步机制包括互斥量、条件变量、原子操作等。

互斥锁 

互斥量(mutex)是一种用于实现多线程同步的机制,用于确保多个线程之间对共享资源的访问互斥。互斥量通常用于保护共享数据的访问,以避免多个线程同时访问同一个变量或者数据结构而导致的数据竞争问题。互斥量提供了两个基本操作:lock() 和 unlock()。当一个线程调用 lock() 函数时,如果互斥量当前没有被其他线程占用,则该线程获得该互斥量的所有权,可以对共享资源进行访问。如果互斥量当前已经被其他线程占用,则调用 lock() 函数的线程会被阻塞,直到该互斥量被释放为止。

#include <iostream>
#include <thread>
#include <mutex>
using namespace std;int share_data = 0;
mutex mtx;
void fun() {for (int i = 0; i < 1000000; ++i) {mtx.lock(); //加锁操作share_data += 1;mtx.unlock(); //解锁操作}
}int main() {thread t1(fun);thread t2(fun);t1.join();t2.join();cout << share_data << endl;return 0;
}

本质上,互斥锁并没有对share_data加锁,而是在每次运行+= 1操作之前,都会判断mtx互斥量是否上锁,如果上锁了则阻塞等待,直到另一边解锁,以此来实现对shared_data变量的访问是安全的。 

线程安全定义:

如果多线程程序每一次的运行结果和单线程运行的结果始终是一样的,那么你的线程就是安全的。也就是把多线程改为单线程之后,运行的结果也不会发生改变。(本质上多线程就是把单线程的任务分割为多个任务,能够安全的分别进行,以此来增加程序的运行效率)

相关文章:

Studying-多线程学习Part1-线程库的基本使用、线程函数中的数据未定义错误、互斥量解决多线程数据共享问题

来源&#xff1a;多线程编程 线程库的基本使用 两个概念&#xff1a; 进程是运行中的程序线程是进程中的进程 串行运行&#xff1a;一次只能取得一个任务并执行这一个任务 并行运行&#xff1a;可以同时通过多进程/多线程的方式取得多个任务&#xff0c;并以多进程或多线程…...

Flink 03 | 数据流基本操作

Flink数据流结构 DataStream 转换 通常我们需要分析的业务数据可能存在如下问题&#xff1a; 数据中包含一些我们不需要的数据 数据格式不方面分析 因此我们需要对原始数据流进行加工&#xff0c;比如过滤、转换等操作才可以进行数据分析。 “ Flink DataStream 转换主要作…...

在 TS 的 class 中,如何防止外部实例化

在 TypeScript&#xff08;TS&#xff09;中&#xff0c;如果你想要防止一个类被外部实例化&#xff0c;你可以采取以下几种策略&#xff1a; 将构造函数设为私有&#xff08;Private Constructor&#xff09;&#xff1a; 通过将类的构造函数设为私有&#xff0c;你可以阻止外…...

HTML详解

HTML 基础HTML 标题HTML 段落HTML 链接HTML 图片HTML 元素HTML 注释HTML 属性HTML 文本格式化HTML 头部HTML cssHTML 表格HTML 列表HTML 自定义列表HTML 区块HTML 表单HTML 框架HTML 颜色HTML 脚本HTML 事件HTML 实体HTML urlHTML5 新元素 新元素 新元素 新元素 新元素 新元素 …...

记录|Modbus-TCP产品使用记录【德克威尔】

目录 前言一、德克威尔1.1 实验图1.2 DECOWELL IO Tester 软件1.3 读写设置1.4 C#进行Modbus-TCP读写 更新时间 前言 参考文章&#xff1a; 使用的第二款Modbus-TCP产品。 一、德克威尔 1.1 实验图 1.2 DECOWELL IO Tester 软件 这也是自带模块配置软件的。下图就是德克威尔的…...

基于深度学习的视频生成

基于深度学习的视频生成是一项极具前景的技术&#xff0c;旨在通过神经网络模型生成逼真的动态视频内容。随着生成对抗网络&#xff08;GANs&#xff09;、自回归模型、变分自编码器&#xff08;VAEs&#xff09;等深度学习模型的发展&#xff0c;视频生成技术已经取得了显著进…...

TB6612电机驱动模块(STM32)

目录 一、介绍 二、模块原理 1.原理图 2.电机驱动原理 三、程序设计 main.c文件 Motor.h文件 Motor.c文件 四、实验效果 五、资料获取 项目分享 一、介绍 TB6612FNG 是东芝半导体公司生产的一款直流电机驱动器件&#xff0c;它具有大电流 MOSFET-H 桥结构&#xff…...

webpack信息泄露

先看看webpack中文网给出的解释 webpack 是一个模块打包器。它的主要目标是将 JavaScript 文件打包在一起,打包后的文件用于在浏览器中使用,但它也能够胜任转换、打包或包裹任何资源。 如果未正确配置&#xff0c;会生成一个.map文件&#xff0c;它包含了原始JavaScript代码的映…...

启动服务并登录MySQL9数据库

【图书推荐】《MySQL 9从入门到性能优化&#xff08;视频教学版&#xff09;》-CSDN博客 《MySQL 9从入门到性能优化&#xff08;视频教学版&#xff09;&#xff08;数据库技术丛书&#xff09;》(王英英)【摘要 书评 试读】- 京东图书 (jd.com) Windows平台下安装与配置MyS…...

微服务_3.微服务保护

文章目录 一、微服务雪崩及解决方法1.1、超时处理1.2、仓壁模式1.3、断路器1.4、限流 二、Sentinel2.1、流量控制2.1.1、普通限流2.1.2、热点参数限流 2.2、线程隔离2.3、熔断降级2.3.1、断路器状态机2.3.2、断路器熔断策略2.3.2.1、慢调用2.3.2.2、异常比例&#xff0c;异常数…...

【设计模式】软件设计原则——依赖倒置合成复用

依赖倒置引出 依赖倒置 定义&#xff1a;高层模块不应该依赖低层模块&#xff0c;二者都应该依赖抽象&#xff1b;抽象不应该依赖细节&#xff0c;细节应该依赖抽象。面向接口编程而不是面向实现编程。 通过抽象使用抽象类、接口让各个类or模块之间独立不影响&#xff0c;实现…...

vue中如何实现组件通信

1. 父子组件通信 1. props和emits 我们最常见的组件通信就是父子组件数据通信。父子组件实现数据通信需要使用props和emit两个api。 在父组件中我们通过props将数据绑定给子组件&#xff0c;在子组件中我们可以通过props对象来收集到父组件传递的数据。 在子组件想要修改的pr…...

C/C++:内存管理

文章目录 前言一、内存分区1. 内存划分情况2. 最大内存计算 二、malloc/calloc/realloc 与 free1. malloc2. calloc3. realloc4. free5. 差异对比6. 失败处理 三、内存分配题目1. 题目2. 内存区域划分 四、C内存管理方式1. new 与 delete2. new/delete操作内置类型3. new和dele…...

jmeter学习(4)提取器

同线程组https://blog.csdn.net/vikeyyyy/article/details/80437530 不同线程组 在JMeter中&#xff0c;正则表达式提取的参数可以跨线程组使用。 通过使用Beanshell后置处理器和属性设置函数&#xff0c;可以将提取的参数设置为全局变量&#xff0c;从而在多个线程组之间共享…...

移动端的每日任务,golang后端数据库应该怎么设计

推荐学习文档 golang应用级os框架&#xff0c;欢迎stargolang应用级os框架使用案例&#xff0c;欢迎star案例&#xff1a;基于golang开发的一款超有个性的旅游计划app经历golang实战大纲golang优秀开发常用开源库汇总想学习更多golang知识&#xff0c;这里有免费的golang学习笔…...

1、Spring Boot 3.x 集成 Eureka Server/Client

一、前言 基于 Spring Boot 3.x 版本开发&#xff0c;因为 Spring Boot 3.x 暂时没有正式发布&#xff0c;所以很少有 Spring Boot 3.x 开发的项目&#xff0c;自己也很想了踩踩坑&#xff0c;看看 Spring Boot 3.x 与 2.x 有什么区别。自己与记录一下在 Spring Boot 3.x 过程…...

Vue根实例、实例总结

在Vue.js框架中&#xff0c;根实例和实例扮演着至关重要的角色。以下是对Vue根实例和实例的总结&#xff1a; Vue根实例 定义与创建&#xff1a; Vue根实例是Vue.js应用的核心。每个Vue应用都是通过用Vue函数创建一个新的Vue实例开始的&#xff0c;这个实例被称为根实例。根实…...

微服务架构:Spring Cloud的服务注册与发现、配置管理、服务网关、熔断器、分布式追踪

微服务架构是一种将应用程序构建为一组小型、自治的服务的方法&#xff0c;每个服务都运行在其独立的进程中&#xff0c;服务间通过轻量级通信机制&#xff08;通常是HTTP API&#xff09;进行通信。Spring Cloud是一套基于Spring Boot的微服务解决方案&#xff0c;它提供了一系…...

Spring Boot实现的大学生就业市场解决方案

1系统概述 1.1 研究背景 如今互联网高速发展&#xff0c;网络遍布全球&#xff0c;通过互联网发布的消息能快而方便的传播到世界每个角落&#xff0c;并且互联网上能传播的信息也很广&#xff0c;比如文字、图片、声音、视频等。从而&#xff0c;这种种好处使得互联网成了信息传…...

Ubuntu上安装Git:简单步骤指南

Git是目前世界上最流行的版本控制系统&#xff0c;广泛用于软件开发中。无论你是开发者还是版本控制的新手&#xff0c;Git都是你不可或缺的工具。本文将为你介绍如何在Ubuntu操作系统上安装Git。 什么是Git&#xff1f; Git是一个开源的分布式版本控制系统&#xff0c;由Lin…...

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型

摘要 拍照搜题系统采用“三层管道&#xff08;多模态 OCR → 语义检索 → 答案渲染&#xff09;、两级检索&#xff08;倒排 BM25 向量 HNSW&#xff09;并以大语言模型兜底”的整体框架&#xff1a; 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后&#xff0c;分别用…...

CVPR 2025 MIMO: 支持视觉指代和像素grounding 的医学视觉语言模型

CVPR 2025 | MIMO&#xff1a;支持视觉指代和像素对齐的医学视觉语言模型 论文信息 标题&#xff1a;MIMO: A medical vision language model with visual referring multimodal input and pixel grounding multimodal output作者&#xff1a;Yanyuan Chen, Dexuan Xu, Yu Hu…...

CentOS下的分布式内存计算Spark环境部署

一、Spark 核心架构与应用场景 1.1 分布式计算引擎的核心优势 Spark 是基于内存的分布式计算框架&#xff0c;相比 MapReduce 具有以下核心优势&#xff1a; 内存计算&#xff1a;数据可常驻内存&#xff0c;迭代计算性能提升 10-100 倍&#xff08;文档段落&#xff1a;3-79…...

Unit 1 深度强化学习简介

Deep RL Course ——Unit 1 Introduction 从理论和实践层面深入学习深度强化学习。学会使用知名的深度强化学习库&#xff0c;例如 Stable Baselines3、RL Baselines3 Zoo、Sample Factory 和 CleanRL。在独特的环境中训练智能体&#xff0c;比如 SnowballFight、Huggy the Do…...

安全突围:重塑内生安全体系:齐向东在2025年BCS大会的演讲

文章目录 前言第一部分&#xff1a;体系力量是突围之钥第一重困境是体系思想落地不畅。第二重困境是大小体系融合瓶颈。第三重困境是“小体系”运营梗阻。 第二部分&#xff1a;体系矛盾是突围之障一是数据孤岛的障碍。二是投入不足的障碍。三是新旧兼容难的障碍。 第三部分&am…...

【笔记】WSL 中 Rust 安装与测试完整记录

#工作记录 WSL 中 Rust 安装与测试完整记录 1. 运行环境 系统&#xff1a;Ubuntu 24.04 LTS (WSL2)架构&#xff1a;x86_64 (GNU/Linux)Rust 版本&#xff1a;rustc 1.87.0 (2025-05-09)Cargo 版本&#xff1a;cargo 1.87.0 (2025-05-06) 2. 安装 Rust 2.1 使用 Rust 官方安…...

DingDing机器人群消息推送

文章目录 1 新建机器人2 API文档说明3 代码编写 1 新建机器人 点击群设置 下滑到群管理的机器人&#xff0c;点击进入 添加机器人 选择自定义Webhook服务 点击添加 设置安全设置&#xff0c;详见说明文档 成功后&#xff0c;记录Webhook 2 API文档说明 点击设置说明 查看自…...

Web中间件--tomcat学习

Web中间件–tomcat Java虚拟机详解 什么是JAVA虚拟机 Java虚拟机是一个抽象的计算机&#xff0c;它可以执行Java字节码。Java虚拟机是Java平台的一部分&#xff0c;Java平台由Java语言、Java API和Java虚拟机组成。Java虚拟机的主要作用是将Java字节码转换为机器代码&#x…...

LRU 缓存机制详解与实现(Java版) + 力扣解决

&#x1f4cc; LRU 缓存机制详解与实现&#xff08;Java版&#xff09; 一、&#x1f4d6; 问题背景 在日常开发中&#xff0c;我们经常会使用 缓存&#xff08;Cache&#xff09; 来提升性能。但由于内存有限&#xff0c;缓存不可能无限增长&#xff0c;于是需要策略决定&am…...

从“安全密码”到测试体系:Gitee Test 赋能关键领域软件质量保障

关键领域软件测试的"安全密码"&#xff1a;Gitee Test如何破解行业痛点 在数字化浪潮席卷全球的今天&#xff0c;软件系统已成为国家关键领域的"神经中枢"。从国防军工到能源电力&#xff0c;从金融交易到交通管控&#xff0c;这些关乎国计民生的关键领域…...