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

通过Wrangler CLI在worker中创建数据库和表

官方使用文档&#xff1a;Getting started Cloudflare D1 docs 创建数据库 在命令行中执行完成之后&#xff0c;会在本地和远程创建数据库&#xff1a; npx wranglerlatest d1 create prod-d1-tutorial 在cf中就可以看到数据库&#xff1a; 现在&#xff0c;您的Cloudfla…...

【RockeMQ】第2节|RocketMQ快速实战以及核⼼概念详解(二)

升级Dledger高可用集群 一、主从架构的不足与Dledger的定位 主从架构缺陷 数据备份依赖Slave节点&#xff0c;但无自动故障转移能力&#xff0c;Master宕机后需人工切换&#xff0c;期间消息可能无法读取。Slave仅存储数据&#xff0c;无法主动升级为Master响应请求&#xff…...

Spring数据访问模块设计

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

.Net Framework 4/C# 关键字(非常用,持续更新...)

一、is 关键字 is 关键字用于检查对象是否于给定类型兼容,如果兼容将返回 true,如果不兼容则返回 false,在进行类型转换前,可以先使用 is 关键字判断对象是否与指定类型兼容,如果兼容才进行转换,这样的转换是安全的。 例如有:首先创建一个字符串对象,然后将字符串对象隐…...

HarmonyOS运动开发:如何用mpchart绘制运动配速图表

##鸿蒙核心技术##运动开发##Sensor Service Kit&#xff08;传感器服务&#xff09;# 前言 在运动类应用中&#xff0c;运动数据的可视化是提升用户体验的重要环节。通过直观的图表展示运动过程中的关键数据&#xff0c;如配速、距离、卡路里消耗等&#xff0c;用户可以更清晰…...

基于 TAPD 进行项目管理

起因 自己写了个小工具&#xff0c;仓库用的Github。之前在用markdown进行需求管理&#xff0c;现在随着功能的增加&#xff0c;感觉有点难以管理了&#xff0c;所以用TAPD这个工具进行需求、Bug管理。 操作流程 注册 TAPD&#xff0c;需要提供一个企业名新建一个项目&#…...

基于SpringBoot在线拍卖系统的设计和实现

摘 要 随着社会的发展&#xff0c;社会的各行各业都在利用信息化时代的优势。计算机的优势和普及使得各种信息系统的开发成为必需。 在线拍卖系统&#xff0c;主要的模块包括管理员&#xff1b;首页、个人中心、用户管理、商品类型管理、拍卖商品管理、历史竞拍管理、竞拍订单…...

腾讯云V3签名

想要接入腾讯云的Api&#xff0c;必然先按其文档计算出所要求的签名。 之前也调用过腾讯云的接口&#xff0c;但总是卡在签名这一步&#xff0c;最后放弃选择SDK&#xff0c;这次终于自己代码实现。 可能腾讯云翻新了接口文档&#xff0c;现在阅读起来&#xff0c;清晰了很多&…...

Vue 模板语句的数据来源

&#x1f9e9; Vue 模板语句的数据来源&#xff1a;全方位解析 Vue 模板&#xff08;<template> 部分&#xff09;中的表达式、指令绑定&#xff08;如 v-bind, v-on&#xff09;和插值&#xff08;{{ }}&#xff09;都在一个特定的作用域内求值。这个作用域由当前 组件…...

华为OD最新机试真题-数组组成的最小数字-OD统一考试(B卷)

题目描述 给定一个整型数组,请从该数组中选择3个元素 组成最小数字并输出 (如果数组长度小于3,则选择数组中所有元素来组成最小数字)。 输入描述 行用半角逗号分割的字符串记录的整型数组,0<数组长度<= 100,0<整数的取值范围<= 10000。 输出描述 由3个元素组成…...