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

news2024/10/4 13:07:29

来源:多线程编程


线程库的基本使用

两个概念:

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

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

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

线程的最大数量取决于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变量的访问是安全的。 

线程安全定义:

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2188058.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Leetcode: 0011-0020题速览

Leetcode: 0011-0020题速览 本文材料来自于LeetCode solutions in any programming language | 多种编程语言实现 LeetCode、《剑指 Offer&#xff08;第 2 版&#xff09;》、《程序员面试金典&#xff08;第 6 版&#xff09;》题解 遵从开源协议为知识共享 版权归属-相同方式…

Java在用增强for循环遍历集合时删除元素,抛出java.util.ConcurrentModificationException异常

文章目录 0. 前言1. 问题产生的背景2. Java中增强for循环的底层原理3. 为什么增强for循环不支持在遍历集合时删除元素3.1 问题排查3.2 modCount 变量的来源3.3 expectedModCount 变量的来源3.4 导致modCount变量和expectedModCount不相等的原因3.5 为什么用迭代器遍历元素时删除…

学籍管理平台|在线学籍管理平台系统|基于Springboot+VUE的在线学籍管理平台系统设计与实现(源码+数据库+文档)

在线学籍管理平台系统 目录 基于SpringbootVUE的在线学籍管理平台系统设计与实现 一、前言 二、系统功能设计 三、系统实现 四、数据库设计 1、实体ER图 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a; 博主介绍&#xff1a;✌️大…

Leetcode: 0021-0030题速览

Leetcode: 0021-0030题速览 本文材料来自于LeetCode solutions in any programming language | 多种编程语言实现 LeetCode、《剑指 Offer&#xff08;第 2 版&#xff09;》、《程序员面试金典&#xff08;第 6 版&#xff09;》题解 遵从开源协议为知识共享 版权归属-相同方式…

如此的“宠物医保”靠谱吗?

今天是世界动物日&#xff0c;本“人民体验官”推广人民日报官方微博文化产品《带着笑意的眼睛&#xff0c;能看见最美的风景》。 截图&#xff1a;来源“人民体验官”推广平台 人民微博说&#xff0c;带着笑意的眼睛&#xff0c;能看见最美的风景。生活中多一点微笑&#xff…

buuctf 部分misc题

rar 这个不多说了直接暴力破解&#xff0c;提示说的已经很清楚了&#xff0c;四位数密码&#xff1b; qr 这个就是一个二维码&#xff0c;用qr扫一下就出来了&#xff1b; 镜子里面的世界 用stegslove&#xff0c;打开dataextract进行调整 调整之后就可以得到flag了 ning…

ipv6之ospf配置

topo图 路由器均使用ipv6的地址 AR1使用默认路由到达ISP&#xff0c;在ospf上使用路由下发功能&#xff0c;把ISP的静态路由下发给内部网络 ISP使用7条静态路由&#xff0c;到达内部网络 AR1、AR2、AR3、AR4之间使用ospfv3进行通信 但是我配了下面的代码无法通信&#xff0…

driver,sequencer,sequence之间的握手关系_2024年10月3日

driver &#xff1a;根据接口协议将事务转换为一组信号级切换的组件。 sequencer &#xff1a;将事务&#xff08;sequence items&#xff09;从 sequence 发送到 driver&#xff0c;并将 driver 的响应反馈给 sequence 的组件。会对同时尝试访问 driver 以激励设计接口的多个 …

什么是 HTTP 请求中的 options 请求?

在 Chrome 开发者工具中的 Network 面板看到的 HTTP 方法 OPTIONS&#xff0c;其实是 HTTP 协议的一部分&#xff0c;用于客户端和服务器之间进行“预检”或“协商”。OPTIONS 请求的作用是让客户端能够获取关于服务器支持的 HTTP 方法和其他跨域资源共享 (CORS) 相关的信息&am…

spring揭秘25-springmvc04-servlet容器与springmvc容器总结

文章目录 【README】【1】DelegatingFilterProxy回顾【1.1】DelegatingFilterProxy初始化过滤器bean 【2】从servlet容器获取springmvc顶级web容器【2.1】从Servlet容器中获取springmvc容器总结【2.2】ContextLoaderListener加载springmvc顶级web容器并将其添加到servlet容器【…

基于SpringBoot+Vue的科研课题项目管理系统源码

文章目录 1.技术架构2.主要功能3.获取方式 1.技术架构 后端&#xff1a;SpringBoot 前端&#xff1a;Vue – Element UI 2.主要功能 登录 /注销、 用户管理、项目管理、申报管理、变更管理、 结题管理、角色管理、权限管理、数据字典等功能 3.获取方式 点击下方名片&a…

android 全面屏最底部栏沉浸式

Activity的onCreate方法中添加 this.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION); Android 系统 Bar 沉浸式完美兼容方案自 Android 5.0 版本&#xff0c;Android 带来了沉浸式系统 ba - 掘金 (juejin.cn)https://juejin.cn/post/7075578…

pycharm中使用anaconda创建多环境,无法将“pip”项识别为 cmdlet、函数、脚本文件或可运行程序的名称

问题描述 用的IDE是&#xff1a; 使用anaconda创建了一个Python 3.9的环境 结果使用pip命令的时候&#xff0c;报错 无法将“pip”项识别为 cmdlet、函数、脚本文件或可运行程序的名称 解决方案 为了不再增加系统变量&#xff0c;我们直接将变量添加在当前项目中你的Ter…

Python3使用cv_bridge转换ROS的image信息

0. Preface 现在很多新的图片处理model都是基于python3的&#xff0c;而ROS还是2.7的&#xff0c;要结合起来用不可避免有很多问题&#xff0c;以下以接收ROS image为例子 以下方法需要用的anaconda&#xff0c;安装方法有很多blog分享 1. Preparation 以下是python3接收ima…

【黑马点评】0.环境配置--Redis6.2.6和可视化工具在Windows上的安装

黑马点评--0.Redis6.2.6在windows上的环境配置与可视化 0 前言1 下载安装2 解压后运行msi文件3 修改配置文件并打开Redis3.1 修改密码&#xff08;可选&#xff09;3.2 测试 4 Redis可视化&#xff08;可选&#xff09;4.1 Another Redis Desktop Manager下载安装4.2 连接Redis…

删除苹果手机所有照片的方法有哪些?

在这个数码摄影盛行的时代&#xff0c;手机相册里可能堆积了成千上万的照片&#xff0c;尤其是苹果手机用户。无论是为了释放存储空间&#xff0c;还是因为想要重新开始整理照片&#xff0c;删除所有照片可能成为一个必要的任务。那么&#xff0c;如何有效地删除删除苹果手机所…

防止老年痴呆的一题

如何只移动三个圆使下图变为正三角? 真相如此简单:

Redis常用命令(超详细整理)

Redis常用命令&#xff08;超详细整理&#xff09; 服务器相关命令 ping &#xff1a; 检测连接是否存活echo&#xff1a; 在命令行打印一些内容quit、exit&#xff1a; 退出客户端shutdown&#xff1a; 退出服务器端info&#xff1a; 返回redis相关信息config get dir/* 实时…

69.【C语言】动态内存管理(重点)(2)

目录 3.free函数 cplusplus网的翻译 提炼要点 使用 x86debug环境下, 打开内存窗口 建议 3.free函数 cplusplus的介绍 点我跳转 cplusplus网的翻译 函数 free void free (void* ptr); 释放内存块 之前通过调用malloc来分配一块内存,calloc和recalloc是来释放内存块的,让内…

Sublime快捷键的使用和修改

sublime快捷键 Ctrl Shift D 复制光标所在整行&#xff0c;插入到下一行Ctrl Shift K 删除整行 如果快捷键冲突了&#xff0c;就需要修改 sublime快捷键修改 示例&#xff1a;当前 Ctrl Shift D 冲突了 1.选择 首选项 -> 按键绑定-默认 2.按住 Ctrl F&#xff0…