当前位置: 首页 > 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…...

深入剖析AI大模型:大模型时代的 Prompt 工程全解析

今天聊的内容&#xff0c;我认为是AI开发里面非常重要的内容。它在AI开发里无处不在&#xff0c;当你对 AI 助手说 "用李白的风格写一首关于人工智能的诗"&#xff0c;或者让翻译模型 "将这段合同翻译成商务日语" 时&#xff0c;输入的这句话就是 Prompt。…...

视频字幕质量评估的大规模细粒度基准

大家读完觉得有帮助记得关注和点赞&#xff01;&#xff01;&#xff01; 摘要 视频字幕在文本到视频生成任务中起着至关重要的作用&#xff0c;因为它们的质量直接影响所生成视频的语义连贯性和视觉保真度。尽管大型视觉-语言模型&#xff08;VLMs&#xff09;在字幕生成方面…...

【android bluetooth 框架分析 04】【bt-framework 层详解 1】【BluetoothProperties介绍】

1. BluetoothProperties介绍 libsysprop/srcs/android/sysprop/BluetoothProperties.sysprop BluetoothProperties.sysprop 是 Android AOSP 中的一种 系统属性定义文件&#xff08;System Property Definition File&#xff09;&#xff0c;用于声明和管理 Bluetooth 模块相…...

【Java_EE】Spring MVC

目录 Spring Web MVC ​编辑注解 RestController RequestMapping RequestParam RequestParam RequestBody PathVariable RequestPart 参数传递 注意事项 ​编辑参数重命名 RequestParam ​编辑​编辑传递集合 RequestParam 传递JSON数据 ​编辑RequestBody ​…...

Spring数据访问模块设计

前面我们已经完成了IoC和web模块的设计&#xff0c;聪明的码友立马就知道了&#xff0c;该到数据访问模块了&#xff0c;要不就这俩玩个6啊&#xff0c;查库势在必行&#xff0c;至此&#xff0c;它来了。 一、核心设计理念 1、痛点在哪 应用离不开数据&#xff08;数据库、No…...

什么是Ansible Jinja2

理解 Ansible Jinja2 模板 Ansible 是一款功能强大的开源自动化工具&#xff0c;可让您无缝地管理和配置系统。Ansible 的一大亮点是它使用 Jinja2 模板&#xff0c;允许您根据变量数据动态生成文件、配置设置和脚本。本文将向您介绍 Ansible 中的 Jinja2 模板&#xff0c;并通…...

laravel8+vue3.0+element-plus搭建方法

创建 laravel8 项目 composer create-project --prefer-dist laravel/laravel laravel8 8.* 安装 laravel/ui composer require laravel/ui 修改 package.json 文件 "devDependencies": {"vue/compiler-sfc": "^3.0.7","axios": …...

#Uniapp篇:chrome调试unapp适配

chrome调试设备----使用Android模拟机开发调试移动端页面 Chrome://inspect/#devices MuMu模拟器Edge浏览器&#xff1a;Android原生APP嵌入的H5页面元素定位 chrome://inspect/#devices uniapp单位适配 根路径下 postcss.config.js 需要装这些插件 “postcss”: “^8.5.…...

wpf在image控件上快速显示内存图像

wpf在image控件上快速显示内存图像https://www.cnblogs.com/haodafeng/p/10431387.html 如果你在寻找能够快速在image控件刷新大图像&#xff08;比如分辨率3000*3000的图像&#xff09;的办法&#xff0c;尤其是想把内存中的裸数据&#xff08;只有图像的数据&#xff0c;不包…...

tomcat指定使用的jdk版本

说明 有时候需要对tomcat配置指定的jdk版本号&#xff0c;此时&#xff0c;我们可以通过以下方式进行配置 设置方式 找到tomcat的bin目录中的setclasspath.bat。如果是linux系统则是setclasspath.sh set JAVA_HOMEC:\Program Files\Java\jdk8 set JRE_HOMEC:\Program Files…...