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

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 多线程编程-小白零基础到手撕线程池-哔哩哔哩

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

贷款利率高低跟什么有关?仅凭身份证就能贷到款?额度是多少?

在金融的广阔舞台上&#xff0c;借款人的“信用基石”——即其综合资质&#xff0c;是决定贷款利率高低的决定性因素。这并非偶然&#xff0c;而是银行基于详尽的风险评估与收益预期所做出的精准判断。 需明确的是&#xff0c;贷款的易得性并不意味着无门槛的放任。它更像是设置…

资料分析笔记(花生)

preparation 资料分析首先最重要的是时间/时间段分小互换 一、速算技巧 加法技巧 1.尾数法 在多个数字精确求和或求差时&#xff0c;从“尾数”入手&#xff0c;为保证精确与速度&#xff0c;一般可观察两位。 求和题目中&#xff0c;若四个选项中后两位都不同&#xff0c;…

通信工程学习:什么是SSB单边带调制、VSB残留边带调制、DSB抑制载波双边带调制

SSB单边带调制、VSB残留边带调制、DSB抑制载波双边带调制 SSB单边带调制、VSB残留边带调制、DSB抑制载波双边带调制是三种不同的调制方式&#xff0c;它们在通信系统中各有其独特的应用和特点。以下是对这三种调制方式的详细解释&#xff1a; 一、SSB单边带调制 1、SSB单边带…

WebAPI (一)DOM树、DOM对象,操作元素样式(style className,classList)。表单元素属性。自定义属性。间歇函数定时器

文章目录 Web API基本认知一、 变量声明二、 DOM1. DOM 树2. DOM对象3. 获取DOM对象(1)、选择匹配的第一个元素(2)、选择匹配多个元素 三、 操作元素1. 操作元素内容2. 操作元素属性(1)、常用属性&#xff08;href之类的&#xff09;(2)、通过style属性操作CSS(3)、通过类名(cl…

ctfshow-php特性(web123-web150plus)

​web123 <?php error_reporting(0); highlight_file(__FILE__); include("flag.php"); $a$_SERVER[argv]; $c$_POST[fun]; if(isset($_POST[CTF_SHOW])&&isset($_POST[CTF_SHOW.COM])&&!isset($_GET[fl0g])){if(!preg_match("/\\\\|\/|\~|…

基于大数据的科研热点分析与挖掘系统

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长 QQ 名片 :) 1. 项目简介 科研活动的快速发展产生了大量的学术文献&#xff0c;如何从这些文献中提炼出有价值的科研热点和趋势成为了一个重要的问题。本项目旨在开发一个基于大数据的科研热点分析可视化系统&#xff0c;采…

Differential Diffusion,赋予每个像素它应有的力量,以及在comfyui中的测试效果

&#x1f97d;原论文要点 首先是原论文地址&#xff1a;https://differential-diffusion.github.io/paper.pdf 其次是git介绍地址&#xff1a;GitHub - exx8/differential-diffusion 感兴趣的朋友们可以自行阅读。 首先&#xff0c;论文开篇就给了一个例子&#xff1a; 我们的方…

Redis 事务:支持回滚吗?深入解析

今天我们要来探讨一个关于 Redis 事务的重要问题&#xff1a;Redis 事务支持回滚吗&#xff1f;这个问题在 Redis 的使用中经常被提及&#xff0c;对于正确理解和使用 Redis 事务至关重要。那么&#xff0c;让我们一起深入解析这个问题吧&#xff01; 一、Redis 事务简介 在了…

tabBar设置底部菜单选项以及iconfont图标

tabBartabBar属性:设置底部 tab 的表现 ​ ​ ​ ​ 首先在pages.json页面写一个tabBar对象,里面放入list对象数组,里面至少要有2个、最多5个 tab, 如果只有一个tab的话,H5(浏览器)依然可以显示底部有一个导航栏,如果没有,需要重启后才有,小程序则报错,只有2个以上才可以…

IDEA加载工程报错Error Loading Project: Cannot load module demo.iml解决

spring boot工程由于工程名字为demo不太好&#xff0c;直接更改了这个工程的名字&#xff0c;主要操作了包括重命名项目文件夹、修改IDEA中的项目名称、模块名称、包名称、以及相关的配置文件等。 然后再打开工程&#xff0c;报错Error Loading Project: Cannot load module de…

瑜伽馆预约系统小程序搭建,全民健身下的市场机遇

随着现代生活水平的提高&#xff0c;人们对健康的要求逐渐提高&#xff0c;瑜伽作为一种修身养性的健身方式&#xff0c;深受大众欢迎。在互联网小程序的普及下&#xff0c;瑜伽馆预约小程序也成为了市场的必然发展趋势&#xff01; 为什么要开发瑜伽馆预约系统&#xff1f; 瑜…

今天又学到了——图编号关联章节号,QGIS下载文件存储的瓦片

记录教程来源&#xff1a;​​​​​​【Word图编号关联章节号】图片分章节 编号&#xff0c;图1-1、图2-1_哔哩哔哩_bilibili 上面链接这个实现的是这个效果&#xff1a; word自动目录及章节自动编号教程_哔哩哔哩_bilibili&#xff0c;这个的效果是自己设计多级列表&#xf…

Redis高级-----持久化AOF、RDB原理

目前已更新系列&#xff1a; 当前&#xff1a;Redis高级-----持久化AOF、RDB原理 Redis高级---面试总结5种数据结构的底层实现 Redis高级----主从、哨兵、分片、脑裂原理-CSDN博客 Redis高级---面试总结内存过期策略及其淘汰策略 计算机网络--面试知识总结一 计算机网络-…

《JavaEE进阶》----11.<SpringIOCDI【Spring容器+IOC详解+DI介绍】>

本篇博客会详细讲解什么是Spring。 SpringIOC SpringID 五个类注解&#xff1a;Controller、Service、Repository、Component、Configuration 一个方法注解&#xff1a;Bean 什么是Spring IOC容器 Spring 是包含众多工具的IOC容器。能装东西的容器。 1.容器 如我们之前学的 Tom…

JavaFX基本控件-TextField

JavaFX基本控件-TextField 常用属性textpromptTextpaddingalignmentwidthheighttooltipbordereditabledisablevisible 格式化整形格式化 实现方式Java实现fxml实现 常用属性 text 设置文本内容 textField.setText("测试数据");promptText 设置文本字段的提示文本&am…

Ollama—87.4k star 的开源大模型服务框架!!

这一年来&#xff0c;AI 发展的越来越快&#xff0c;大模型使用的门槛也越来越低&#xff0c;每个人都可以在自己的本地运行大模型。今天再给大家介绍一个最厉害的开源大模型服务框架——ollama。 项目介绍 Ollama 是一个开源的大语言模型&#xff08;LLM&#xff09;服务工具…

替换Windows AD时,网络准入场景如何迁移对接国产身份域管?

Windows AD是迄今为止身份管理和访问控制领域的最佳实践&#xff0c;全球约90%的中大型企业采用AD作为底层数字身份基础设施&#xff0c;管理组织、用户、应用、网络、终端等IT资源。但随着信创建设在党政机关、金融、央国企、电力等各行各业铺开&#xff0c;对Windows AD域的替…

swagger简单使用学习

注意 一下基于spring-boot 3.0.2版本&#xff0c;该版本不支持springfox-swagger2 2.9.2会报错&#xff0c;无法访问swagger 安装 在pomx文件中添加对应的依赖 <!-- swagger --><dependency><groupId>org.springdoc</groupId><artifactId>spr…

Superset二次开发之Select 筛选器源码分析

路径&#xff1a;superset-frontend/src/filters/components/Select 源码文件&#xff1a; 功能点&#xff1a; 作用 交互 功能 index.ts作为模块的入口点,导出其他文件中定义的主要组件和函数。它使其他文件中的导出可以被外部模块使用。 SelectFilterPlugin.tsx 定义主要…

PostgreSQL的repmgr工具介绍

PostgreSQL的repmgr工具介绍 repmgr&#xff08;Replication Manager&#xff09;是一个专为 PostgreSQL 设计的开源工具&#xff0c;用于管理和监控 PostgreSQL 的流复制及实现高可用性。它提供了一组工具和实用程序&#xff0c;简化了 PostgreSQL 复制集群的配置、维护和故障…