C++11线程池、多线程编程(附源码)

news2024/11/13 10:13:54

Test1

示例源码展示:

#include<iostream>
#include<thread>
#include<string>
using namespace std;

void printHelloWord(string s)
{
	cout << s << endl;
	//return;
}
int main()
{
	string s;
	s = "wegfer";
	thread thread1(printHelloWord,s);
	//thread1.join();//在线程没有结束的情况下保持主线程一直工作着,不会往下运行导致主线程结束了,线程还没结束。
	//thread1.detach();//分离子线程和主线程,主线程结束了子线程还在后台运行,也就是成为孤儿进程
	bool isJoinable = thread1.joinable();//返回一个布尔值,表明这个线程是不是可以调用join或者detach功能,一旦线程用过join或者detach功能,返回值就是0
	cout << isJoinable << endl;//输出结果1
	
	if (isJoinable)
	{
		thread1.join();
	}
	bool isJonable_2 = thread1.joinable();
	cout << isJonable_2 << endl;//输出结果0

	system("pause");
	return 0;
}

函数:

  1. .join():阻塞线程,当线程没有运行结束的时候主线程一直停着不关闭,防止子线程还没运行完,主线程结束了报错
  2. .detach():分离子线程和主线程,主线程结束了子线程还在后台运行,也就是成为孤儿进程
  3. joinable():返回一个布尔值,表明这个线程是不是可以调用join或者detach功能,一旦线程用过join或者detach功能,返回值就是0

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

1.传递临时变量问题

void foo(int & x)
{
	x = x + 1;
}
void test02()
{
	int a = 1;
	/*
	thread t(foo, a);
	t.join();
	cout << a << endl;//运行报错,因为t(foo, a)默认情况下传的是值,不是引用,改成ref(a)就可以
	*/
	thread t(foo, ref(a));
	t.join();
	cout << a << endl;//输出结果2
}

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

#include<iostream>
#include<thread>
#include<string>
using namespace std;

int a = 1;
thread t;
void fool(int &x)
{
	x = x + 1;
}

void test()
{
	//int a = 1;//会报错,但也不一定,不同环境下有可能也不会,但是这是不安全的情况,所以最好还是放外面
	t = thread(fool, ref(a));//a在栈里面,如果test先于foo执行完毕,那么a就释放掉了,线程就报错了。把a放到外面就好了
}

int main()
{
	test();
	t.join();
	system("pause");
	return 0;
}

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

void fool(int *x)
{
	*x = *x + 1;
	
}
int main()
{
	int *ptr = new int(1);
	thread t(fool, ptr);//这种代码崩溃不崩溃看运气,而且还会出现可能不崩溃,但是已经不按照预想的情况运行了,所以不要这么写
	delete ptr;//这里已经释放了,万一线程还没运行完,他在去这里找,已经不确保正确性了
	t.join();
	system("pause");
	return 0;
}

4.类成员函数作为入口函数,类对象被提前释放
类似于上面的3
解决办法:智能指针

#include<iostream>
#include<thread>
#include<string>
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);//当使用 std::thread 来执行成员函数时,调用类的成员函数与调用普通的函数不一样。成员函数需要一个对象实例来调用,因为它是绑定到特定对象的,因此在调用时必须传递一个指向该对象的指针或引用,所以这里A::foo用引用
	t.join();
	system("pause");
}

5.入口为类的私有成员函数
一下报错,无法运行


class A {
private:
	void foo()
	{
		cout << "Hello" << endl;
	}
};
int main()
{
	shared_ptr<A> a = make_shared<A>();
	thread t(&A::foo, a);//当使用 std::thread 来执行成员函数时,调用类的成员函数与调用普通的函数不一样。成员函数需要一个对象实例来调用,因为它是绑定到特定对象的,因此在调用时必须传递一个指向该对象的指针或引用
	t.join();
	system("pause");
}

解决办法:友元,或者通过调用类内的公有函数在调用私有函数

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);//当使用 std::thread 来执行成员函数时,调用类的成员函数与调用普通的函数不一样。成员函数需要一个对象实例来调用,因为它是绑定到特定对象的,因此在调用时必须传递一个指向该对象的指针或引用
	t.join();
}
int main()
{
	thread_foo();
	
	system("pause");
}

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

下面这样的代码又安全隐患,有可能是我们想要的20000,也有可能不是。

int a = 0;
void func()
{
	for (int i = 0; i < 10000; i++)
	{
		a = a + 1;
	}
}
int main()
{
	thread t1(func);
	thread t2(func);
	t1.join();
	t2.join();
	cout << a << endl;

	system("pause");
	return 0;
}

解决办法:加锁,互斥锁

int a = 0;
mutex mtx;
void func()
{
	for (int i = 0; i < 10000; i++)
	{
		mtx.lock();//加锁
		a = a + 1;
		mtx.unlock();//解锁
	}
}
int main()
{
	thread t1(func);
	thread t2(func);
	t1.join();
	t2.join();
	cout << a << endl;

	system("pause");
	return 0;
}

test5 lock_guard与unique_lock

lock_guard: C++标准库中的一种互斥量封装类,用于保护共享数据,防止多个线程同时访问同一资源而导致数据竞争问题。
特点:构造函数被调用时,该互斥量会被自动锁定
析构函数被调用时,该互斥量会被自动解锁
lock_guard对象不能复制或移动,因此它只能在 局部作用域中使用

void func()
{
	for (int i = 0; i < 10000; i++)
	{
		lock_guard<mutex> lg(mtx);//lock_guard标准库中的类,可以管理锁的自动释放。它会在构造函数中自动尝试锁定 mtx,并在该作用域结束时,自动调用析构函数来释放 mtx 锁。这一轮训话结束就自动解锁了
		a = a + 1;
	}
}
int main()
{
	thread t1(func);
	thread t2(func);
	t1.join();
	t2.join();
	cout << a << endl;

	system("pause");
	return 0;
}

unique_lock: C++标准库中的一个互斥封装类,用于在多线程程序中对互斥量进行加锁和解锁操作。它的主要特点是可以对互斥量进行更灵活的管理,包括延迟加锁、条件变量、超时等。
unique_lock:提供的成员函数:
lock():尝试对互斥量进行加锁操作,如果当前互斥量已经被其他线程持有,则当前线程被阻塞,直到互斥量被成功加锁。
try_lock():尝试对互斥量进行加锁,如果当前互斥量已经被其他线程持有,则函数立刻返回false,否则返回true。
try_lock_for():尝试对互斥量进行加锁,如果当前互斥量已经被其他线程持有,则当前线程会被阻塞,直到互斥量被成功加锁,或者超过了指定的时间。
tyr_lock_until():尝试对互斥量进行加锁操作,如果互斥量已经被其他线程持有,则当前线程会被阻塞,直到互斥量被成功加速,或者超过了指定的时间。
unlock():对互斥量进行解锁操作。

void func()
{
	for (int i = 0; i < 10000; i++)
	{
		unique_lock<mutex> lg(mtx);//也会成功加锁
		a = a + 1;
	}
}
void func()
{
	for (int i = 0; i < 10000; i++)
	{
		unique_lock<mutex> lg(mtx,defer_lock);//成功构造但是不加锁
		a = a + 1;
	}
}

输出结果:
在这里插入图片描述
try_lock_for():

int a = 0;
timed_mutex mtx;
void func()
{
	for (int i = 0; i < 2; i++)
	{
		unique_lock<timed_mutex> lg(mtx,defer_lock);//成功构造但是不加锁
		if (lg.try_lock_for(chrono::seconds(2)))//等待一段时间,这段时间等不到就直接返回,等到了就运行获取值。
		{
			std::this_thread::sleep_for(chrono::seconds(3));
			a = a + 1;
		}//延迟加锁,5秒内没加上就不加了
		
		
	}
}
int main()
{
	thread t1(func);
	thread t2(func);
	t1.join();
	t2.join();
	cout << a << endl;

	system("pause");
	return 0;
}

锁只是不允许获取资源,不是让你加不上锁就直接返回结束你的线程,而是加不上锁的话那就返回false,然后继续后续操作。

int a = 0;
timed_mutex mtx;
void func()
{
	for (int i = 0; i < 2; i++)
	{
		unique_lock<timed_mutex> lg(mtx, defer_lock);//成功构造但是不加锁
		lg.try_lock_for(chrono::seconds(2));//延迟加锁,5秒内没加上就不加了
		std::this_thread::sleep_for(chrono::seconds(3));
		a = a + 1;

	}
}
int main()
{
	thread t1(func);
	thread t2(func);
	t1.join();
	t2.join();
	cout << a << endl;//输出结果为4

	system("pause");
	return 0;

}

Test06 call_once与其使用场景

单例设计模式是一种常见的设计模式,用于确保某个类只能创建一个实例。由于单例实例是全局唯一的,因此在多线程环境中使用单例模式时,需要考虑线程安全问题。
最常见的是日志类,全局只有一个日志类,日志类可以打印各种信息。
只可以在线程函数中使用call_once
生产者-消费者问题的实现:

#include<iostream>
#include<thread>
#include<string>
#include<mutex>
#include<condition_variable>
#include<queue>
#include<vector>
#include<functional>

using namespace std;
queue<int> g_queue;
condition_variable g_cv;
mutex mtx;
void Producer()
{
	for (int i = 0; i < 10; i++)
	{
		unique_lock<mutex>lock(mtx);
		g_queue.push(i);
		//队列为空之后加任务
		g_cv.notify_one();
		cout << "task:"<<i << endl;
	}
}
void Consumer()
{
	while (1)
	{
		unique_lock<mutex>lock(mtx);
		bool isempty = g_queue.empty();
		//g_cv.wait(lock, !isempty);//队列不空的时候消费者继续取
		g_cv.wait(lock, []()
			{
				return !g_queue.empty();
			});

		int value = g_queue.front();
		g_queue.pop();
		cout << "value: " << value << endl;
	}
}

int main()
{
	thread t1(Producer);
	thread t2(Consumer);
	t1.join();
	t2.join();
	system("pause");
	return 0;
}

运行结果:
在这里插入图片描述
**注:**也有可能是生产者生产完了商品消费者才开始消费,这是因为生产者和消费者是独立的两个线程,他们的运行顺序并不总是由我们决定的,操作系统可能会调度他们,有可能是操作系统衡量之后觉得应该让生产者先生产。
出现原因:1.线程调度不确定性:线程的执行顺序有操作系统调度,那次运行可能操作系统觉得应该 让生产者进行生产。
2.生产者的生产速度大于消费者消费速度,可以在生产者那里加一个sleep_for,增加生产者的生产时间(生产者只需要push,而消费者还需要wait)

Test07 生产者与消费者问题

生产者和消费者二者一个创建任务一个取走任务,他们一个操纵任务队列的头部一个操纵任务队列的尾部,任务队列需要互斥访问,1. 因为生产者、消费者对任务队列执行取出或添加的操作后需要更改队列的指针或进行一些内部结构的更新,如果不互斥有可能会使得队列崩溃。2. 防止“写-读”冲突,生产者还未完全把任务送到任务队列,消费者就已经在取这个任务了,就导致读到不完整的数据。3.防止“空队列”或者“满队列”竞争,队列是空还是满需要专门的检测,状态的检测和队列的更新是分开操作,需要分开,如果没有锁,可能队列已经满了,但是还没来得及更新状态,但是生产者取了状态信息认为没有满,然后继续添加导致溢出。同样的情况也在任务队列空的时候。

Test08 C++11跨平台线程池的实现

有一个存储着任务的队列,我们提前创建好的线程池不停的去执行这些任务,线程池存在的意义是因为线程的创建和销毁都是很耗费资源时间的,所以我提前弄好就不用消耗这些,提高效率

问题描述:
在这里插入图片描述

线程池的实现(使用了C++11的新特性):

#include<iostream>
#include<thread>
#include<string>
#include<mutex>
#include<condition_variable>
#include<queue>
#include<vector>
#include<functional>

using namespace std;

class ThreadPool {
public:
	ThreadPool(int numThreads) :stop(false)
	{
		for (int i = 0; i < numThreads; i++)
		{
			threads.emplace_back([this]
			{
				while (1)
				{
					unique_lock<mutex>lock(mtx);
					condition.wait(lock, [this]
					{
						return !tasks.empty() || stop;

						});
					if (stop && tasks.empty())
					{
						return;
					}
					function<void()> task(move(tasks.front()));
					tasks.pop();
					lock.unlock();
					task();
				}
				});
		}
	}
	~ThreadPool() {
		{
			unique_lock<mutex>lock(mtx);
			stop = true;
		}
	
		condition.notify_all();
		for (auto &t : threads)
		{
			t.join();
		}
	}
	template<class  F,class ... Args>
	void enqueue(F && f, Args&&... args)
	{
		function<void()>task = bind(forward<F>(f), forward<Args>(args)...);
		{
			unique_lock < mutex>lock(mtx);
			tasks.emplace(move(task));
		}
		condition.notify_one();
	}
	

	
private:
	vector<thread> threads;
	queue<function<void()>> tasks;
	mutex mtx;
	condition_variable condition;
	bool stop;

};

int main()
{
	ThreadPool pool(4);
	for (int i = 0; i < 10; i++)
	{
		pool.enqueue([i]
			{
				cout << "task: " << i << "is running " << endl;
				this_thread::sleep_for(chrono::seconds(1));
				cout << "task: " << i << "is done" << endl;
			});
	}
	system("pause");
	return 0;

}

运行截图:
在这里插入图片描述
注: 八手动解锁关了就能解决打印混乱的问题
参考文献:程序员陈子青-C++11 多线程编程-小白零基础到手撕线程池-哔哩哔哩

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

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

相关文章

【动手学深度学习】08 线性回归 + 基础优化算法(个人向笔记)

1. 线性回归 一个简化的模型&#xff1a; 我们可以这样来定义线性模型&#xff1a;注意这里先转置变成了列向量线性模型可以被看成时一个单层的神经网络&#xff1a;单层是因为单层参数 - 用一个函数来衡量预估质量&#xff1a;损失函数 在训练的时候寻找最小化的损失的参数 w…

Vivado编译报错黑盒子问题

1 问题描述 “Black Box Instances: Cell **** of type ** has undefined contents and is considered a back box. The contents of this cell must be defined for opt_design to complete successfully.” 检查工程代码提示的模块&#xff0c;该模块为纯手写的Veril…

使用AI赋能进行软件测试-文心一言

1.AI赋能的作用 提高速度和效率缺陷预测与分析 2.AI互动指令格式--文心一言 角色、指示、上下文例子、输入、输出 a 直接问AI 针对以下需求&#xff0c;设计测试用例。 需求&#xff1a; 1、账号密码登录系统验证账号和密码的正确性。 验证通过,用户登录成功,进入个人中心;验…

【优选算法】---前缀和

前缀和 一、【模板】一维前缀和二、【模板】二维前缀和三、寻找数组的中心下标四、除自身以外数组的乘积五、和为K子数组六、和可被 K 整除的子数组七、连续数组八、矩阵区域和 一、【模板】一维前缀和 一维前缀和&#xff0c;链接 1、预处理出来一个前缀和数组 注意&#xf…

C#学习 深入理解委托、匿名方法、Lamda表达式、Linq;

目录 一.委托 1.1 什么是委托 1.2 委托的使用 二.匿名方法和Lamda表达式 2.1 什么是匿名方法 2.2 Lambda表达式 三.Linq 3.1 Linq理解 3.2 Linq的扩展方法 一.委托 1.1 什么是委托 委托和类一样&#xff0c;是具有特定参数列表和返回值类型的方法函数的…

VSCode连接docker

1.启动ssh服务 vim /root/.bashrc 或者 vim ~/.bashrc /usr/sbin/sshd #启动ssh服务~代表主目录&#xff0c;cd ~会返回root目录 cd / 返回最根上的目录 为了防止每次打开容器都要输入此指令&#xff0c;我们直接在 ~/.bashrc文件最后一行添加sshd启动命令即可。 打开终端…

javase小项目--图书管理系统

前面我们已经学习到了javase的基本语法结构-继承&#xff0c;多态&#xff0c;接口&#xff0c;接下来就让我们大家一起来利用这些来手动实现一个小项目——简洁的图书管理系统 目录 1.思路 1.book类 1.book 2.booklist 2.user类 user AdminUser NormalUser 3.ioperat…

如何使用Pytorch-Metric-Learning?

文章目录 如何使用Pytorch-Metric-Learning&#xff1f;1.Pytorch-Metric-Learning库9个模块的功能1.1 Sampler模块1.2 Miner模块1.3 Loss模块1.4 Reducer模块1.5 Distance模块1.6 Regularizer模块1.7 Trainer模块1.8 Tester模块1.9 Utils模块 2.如何使用PyTorch Metric Learni…

传统CV算法——基于harris检测算法实现角点检测

角点 角点是图像中的一个特征点&#xff0c;指的是两条边缘交叉的点&#xff0c;这样的点在图像中通常表示一个显著的几角。在计算机视觉和图像处理中&#xff0c;角点是重要的特征&#xff0c;因为它们通常是图像中信息丰富的区域&#xff0c;可以用于图像分析、对象识别、3D…

JavaScript 循环控制语句-break和continue

break循环 首先i0&#xff0c;判断i是否<5,满足条件&#xff0c;判断i是否等于3&#xff0c;i不等于3&#xff0c;输出i0&#xff0c;i的值加1&#xff0c;判断i是否<5&#xff0c;判断i是否等于3&#xff0c;i不等于3&#xff0c;输出i1&#xff0c;i的值加1&#xff0c…

【H2O2|全栈】关于HTML(6)HTML基础(五 · 完结篇)

HTML基础知识 目录 HTML基础知识 前言 准备工作 标签的具体分类&#xff08;五&#xff09; 本文中的标签在什么位置中使用&#xff1f; 表单&#xff08;二&#xff09; 下拉选择菜单 文本域 案例 拓展标签 iframe框架 案例 预告和回顾 后话 前言 本系列博客介…

EasyExcel模板导出与公式计算(下)

目录 环境要求 功能预览 需求分析 导入依赖 制作模板 编写代码 格式优化 最终效果 总结 在上一篇 EasyExcel模板导出与公式计算&#xff08;上&#xff09;-CSDN博客 文章中我们知道了在若依中使用自带的Excel注解来实现表格数据的导出&#xff0c;并且通过重写相关接…

C++复习day07

一、继承 1.什么是继承&#xff1f;继承的意义是什么&#xff1f; 继承的概念 继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段&#xff0c;它允许程序员在保持原有类特性的基础上进行扩展&#xff0c;增加功能&#xff0c;这样产生新的类&#xff0c…

C++ STL 适配器

系列文章目录 模板特例化&#xff0c;偏特化&#xff0c;左右值引用 https://blog.csdn.net/surfaceyan/article/details/126794013 C STL 关联容器 https://blog.csdn.net/surfaceyan/article/details/127414434 C STL 序列式容器(二) https://blog.csdn.net/surfaceyan/arti…

项目实战系列三: 家居购项目 第四部分

购物车 &#x1f333;购物车&#x1f346;显示购物车&#x1f346;更改商品数量&#x1f346;清空购物车&&删除商品 &#x1f333;生成订单 &#x1f333;购物车 需求分析 1.会员登陆后, 可以添加家居到购物车 2.完成购物车的设计和实现 3.每添加一个家居,购物车的数量…

比较顺序3s1,3s2,4s1之间的关系

(A,B)---6*30*2---(0,1)(1,0) 分类A和B&#xff0c;让B全是0。当收敛误差为7e-4&#xff0c;收敛199次取迭代次数平均值&#xff0c;3s1为 3s2为 4s1为 3s1&#xff0c;3s2&#xff0c;4s1这3个顺序之间是否有什么联系 &#xff0c; 因为4s1可以按照结构加法 变换成与4s1内在…

Linux相关概念和重要知识点(2)(用户、文件和目录、inode、权限)

1.root和普通用户 在Windows里面&#xff0c;管理员Administrator是所有用户里面权限最高的&#xff0c;很多文件都会提示请使用管理员打开等。但在整个Windows系统中&#xff0c;管理员的权限并不是最大的&#xff0c;System优先级更高&#xff0c;因此我们系统中的某些文件是…

谈谈ES搜索引擎

一 ES的定义 ES 它的全称是 Elasticsearch&#xff0c;是一个建立在全文搜索引擎库Lucene基础上的一个开源搜索和分析引擎。ES 它本身具备分布式存储&#xff0c;检索速度快的特性&#xff0c;所以我们经常用它来实现全文检索功能。目前在 Elastic 官网对 ES 的定义&#xff0c…

模拟实现vector中的常见接口

insert void insert(iterator pos, const T& x) {if (_finish _endofstorage){int n pos - _start;size_t newcapacity capacity() 0 ? 2 : capacity() * 2;reserve(newcapacity);pos _start n;//防止迭代器失效}int end _finish-1;while (end > pos){*(end 1…

PMBOK® 第六版 规划进度管理

目录 读后感—PMBOK第六版 目录 规划进度管理主要关注为整个项目期间的进度管理提供指南和方向。以下是两个案例&#xff0c;展示了进度管理中的复杂性和潜在的冲突&#xff1a; 案例一&#xff1a;近期&#xff0c;一个长期合作的客户因政策要求&#xff0c;急需我们为多家医…