c++多线程学习笔记二——线程池的实现


前言

手写线程池需要了解基本知识点 。


一、互斥量

多线程在同步访问共享数据的情况下,如果多个线程同时读取数据内容,各个线程可以正常运行,但如果有的线程对数据进行更改,则会造成程序崩溃的情况。为了处理这种情况,我们采用互斥量对数据进行保护。
互斥量:能够对一段代码片段进行保护,具体用法如下:

#include<mutex>//头文件引入
mutex  mylock;
mylock.lock()
mylock.unlock()
lock和unlock必须成对使用,加所和解锁的次数必须相同,加锁期间保证当前的的线程操作不会被打断。
//

lock_guard

lock_guard<mutex> lock(mylock)//这个锁可以自己在析构函数中解锁,避免自己手动解锁

unique_lock

unique_lock<mutex>lock(mylock);

也是一个类包装好的锁,但是相比lock更加灵活,可以有额外的参数
1)adopt_lock() 收养这个锁,也就是说,前面这个锁已经被上锁了,这里只是有领养权,帮助解锁

2)try_to_lock() 尝试去锁,因为如果很多资源去抢占锁,如果没抢占到锁会在锁上进行等待比较浪费资源,所以设置了这个变量。如果没锁成功也会返回,提高执行效率,可以通过owns_lock()判断是否上锁。

unique_lock<mutex>my_lcok(mylock);

3)defer_lock(),延迟延迟上锁,声明一个互斥量,之后在自己上锁。

unique_lock<mutex>my_lock(mylock,defer_lock);
do_something();
my_lock.lock();
my_lock.unlock();

原子操作
原子操作与互斥量类似,定义后在处理时不会被打断,我理解为自带锁。但是访问速度要比直接上锁快很多。

二、条件变量

c++标准库提供的工具去等待事件的发生,通过另一线程触发等待事件的基本的唤醒方式,这种机制称为条件变量。一个条件变量会与多个事件或者其他条件相关,并且一个或者多个线程会等待条件的达成。

std::conditional_variable data_cond; //声明一个条件变量

条件变量通常与 unique_lock配合使用,。线程之后会调用std::condition_variable 成员函数的wait(),传递一个锁和一个lambda函数表达式,wait()函数中的谓词函数条件满足时返回,如果条件不满足,wait()将解锁互斥量,并将这个线程置为阻塞或者等待,直到线程用notify_one()通知条件变量。 这也是为什么要使用unique_lock而不使用lock_guard等待中的线程必须解锁互斥量,并在这之后对互斥量再次上锁,而lock_guard 没有这么灵活,如果互斥量在线程休眠期间保持锁定状态,准备数据的线程无法锁住互斥量。以下是我用条件变量创建的简单生产,单消费的模型例子

# include<iostream>
# include<thread> //引入多线程包含的头文件
#include<vector>
#include<queue>
#include<list>
#include<stack>
#include<mutex>
using namespace std;
//线程函数

list<int> data_1;
mutex data_mutex;
void InsertData()
{
	int count = 100000;
	for(int i=0;i<count;i++)
	{
		cout << "插入数据" << i << endl;
		std::unique_lock<std::mutex> temp(data_mutex);
		if (temp.owns_lock())
			data_1.push_back(i);
	
	
	}
}

void ConsumeData()
{
	int count = 1000000;
	for (int i = 1; i <= count; i++)
	{

		std::unique_lock<std::mutex> temp(data_mutex);
		if (temp.owns_lock())
		{
			if (!data_1.empty())
			{
				cout << "数据为" << data_1.front() << endl;
				data_1.pop_front();

			}
		}


	;
		
	}
}
;
class Produce_Consume
{
std:: mutex my_mutex;
std::condition_variable data_is_full_con;
std::condition_variable data_is_empty_con;
queue<int>  data_queue;
int size;

public:

	Produce_Consume(int _size = 100) :size(_size){}
	void Produce_Thread(int value)
	{  
		while (true)
		{
			
			std::unique_lock<std::mutex> Produce_Con(my_mutex);
			data_is_full_con.wait(Produce_Con, [=]() {return data_queue.size() <= size; });
			cout << "生产者线程已经生产,生产值为 " << value << endl;
			data_queue.push(value++);
			
			data_is_empty_con.notify_one();
		
		}
		
	}

	void Consume_Thread()
	{
		cout << "IP " << this_thread::get_id << endl;
		while (true)
		{
			std::unique_lock<std::mutex> Consume_Con(my_mutex);
			data_is_empty_con.wait(Consume_Con, [=]() {return data_queue.size() > 0; });
			cout << "消费者线程数据是 "  <<data_queue.front()<<" ID "<< this_thread::get_id << endl;
			data_queue.pop();
			Consume_Con.unlock();
			data_is_full_con.notify_one();
		
		}
		
	}
};
int main()
{   
	
	//thread consume(ConsumeData);
	/*thread produce(InsertData);
	


	produce.join();
	consume.join();*/
	Produce_Consume data_function;
	std::thread Produce(&Produce_Consume::Produce_Thread, &data_function,1);
	std::thread Consume(&Produce_Consume::Consume_Thread, &data_function);
	std::thread Consume1(&Produce_Consume::Consume_Thread, &data_function);

	Produce.join();
	Consume.join();
	Consume1.join();
}

三.其他语句

1)future期望值
c++中有两种期望值,使用两种类型模板实现,声明在中,std::future实例只能与一个指定时间相关联,这个future最终计算的结果,当需要这个值时,只需要调用这个对象的get()成员函数,并且会阻塞线程知道期望值状态为止。

#include <future>
#include<iosteam>
int return_int()
int main()
{
std::future<int> show=std::async(return_int);
cout << show.get() << endl;
}

2)任务与期望值相关联
std::packaged_task<> 对一个函数或可调用对象,绑定一个期望值,当当调用 std::packaged_task<>对象时,它就会调用相关函数或者可调用对象,将期望值设置为就绪,返回值也会被存储,这可以用在构建线程池的结构单元。
当构建std::packaged_task<>实例时,就必须传入一个相关函数或者可调用对象,这个函数或者可调用对象,需要能接收指定参数和返回可转换为指定返回类型的值。

3)线程池代码
线程池有一个任务队列,有一个工作线程,每次抓取一个任务执行。下面是实现的详细代码,其中会有一些c++新特性在里面,所以读起来不是很顺畅,下面是一些有用的参考可以解答。
线程池GitHub
匿名函数
知乎
简单笔记记录
c++并发编程

#include<iostream>
#include<queue>
#include<mutex>
#include<future>
#include<vector>
#include <thread>
using namespace std;
class Thread_Pool
{
	vector<thread> Works;
	queue<std::function<void()>>Tasks;
	mutex my_mutex;
	condition_variable Tasks_is_empty;
	bool isStop;
	int Thread_size;

public:
	Thread_Pool(int _size = 1, bool _isStop = false) :Thread_size(_size), isStop(_isStop) {
		for (int i = 0; i < Thread_size; i++)
		{
			
			Works.emplace_back([this]() {
				while (1)
				{
					function<void()>task;
					{
						
						unique_lock<mutex> my_lock(my_mutex);
						Tasks_is_empty.wait(my_lock, [this]() {return isStop || !Tasks.empty(); });
						if (isStop && Tasks.empty())
							return;
						task = std::move(Tasks.front());
						Tasks.pop();
					}
					task();
				}
				});
		}
	};

	~Thread_Pool()
	{
		{
			unique_lock<std::mutex> my_lock(my_mutex);
			isStop = true;
		}
		Tasks_is_empty.notify_all();
		for (auto& work : Works)
		{
			work.join();
		}

	}

	template<typename F,typename... Args>
	auto submit(F&& f, Args&&... args)->std::future<decltype(f(args...))>
	{
		auto Ptr = make_shared<std::packaged_task<decltype(f(args...))() > >(std::bind(std::forward<F>(f), std::forward<Args>(args)...));

		{
			unique_lock<std::mutex>my_lock(my_mutex);
			if (isStop) throw std::runtime_error("error!!!!");
			Tasks.emplace([Ptr]() {(*Ptr)(); });
		}
		Tasks_is_empty.notify_one();
		return Ptr->get_future();

	}


};
void test(int a, int b)
{
	cout << "a*B " << a * b << endl;
}
int main()
{
	Thread_Pool pool(4);
	auto res = pool.submit(test, 2, 4);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值