手写最简单的线程池

目录

threadsafe_queue.hpp:

thread_pool_easy.hpp:

测试代码:


代码和个人遇到的一些小问题,详细解释可以参考上一篇博客 

threadsafe_queue.hpp:

//实现线程安全的队列容器(通过封装一个queue)
//成员变量:
//    1.通过mutex互斥对pop、push操作加锁,从而实现“安全”
//    2.内部封装的queue,queue容器内部存储share_ptr,数据通过share_ptr间接存储好处:
//        将每个数据的share_ptr初始化放在push()中,wait_and_pop就算异常安全的
//        对share_ptr实例分配内存的操作可以脱离锁的保护,有利于增强性能
//    3.条件变量等待条件成立(pop之前必须先push)
//成员函数:
//    1.构造函数
//    2.push
//    3.pop 通过函数参数传递要pop的数据:
//        两种pop,try_pop立即返回,即使队列为空(通过返回值表示操作失败)
//        wait_and_pop 一直等到有数据才pop
//    4.通过返回值传递要pop的数据
//    5.empty
//

#ifndef _THREADSAFE_QUEUE_HPP_
#define _THREADSAFE_QUEUE_HPP_

#include
#include
#include
#include
template
class threadsafe_queue{
	mutable std::mutex mut;
	std::queue> queue_data;
	std::condition_variable data_cond;
public:
	threadsafe_queue(){};
	void push(T data){ //push(T& data)
		//std::share_ptr data_ptr = std::make_share(data);
		std::shared_ptr data_ptr(std::make_shared(std::move(data)));
		std::lock_guard lk(mut);
		queue_data.push(data_ptr);
		data_cond.notify_one();
	}
	bool try_pop(T& value){
		std::lock_guard lk(mut);
		if(queue_data.empty())
			return false;
		else{
			value = std::move(*queue_data.front());
			queue_data.pop();
			return true;
		}
	}
	void wait_and_pop(T& value){
		std::lock_guard lk(mut);
		//data_cond.wait(lk,[=](){return this->empty()}); 1.
		data_cond.wait(lk,[this]{return !queue_data.empty();});
		value = std::move(*queue_data.front());
		queue_data.pop();
	}

	std::shared_ptr try_pop(){
		//std::shared_ptr res 2.
		std::lock_guard lk(mut);
		if(queue_data.empty())
			return nullptr;//return shared_ptr() 3.
		else{
			//std::shared_ptr res = queue_data.front();
			std::shared_ptr res = std::move(queue_data.front());
			queue_data.pop();
			return res;
		}
	}
	std::shared_ptr wait_and_pop(){
		std::lock_guard lk(mut);
		data_cond.wait(lk,[this]{return !queue_data.empty();});
		std::shared_ptr res = std::move(queue_data.front());
		queue_data.pop();
		return res;
	}
	bool empty() const{
		std::lock_guard lk(mut);
		return queue_data.empty();
	}
};
#endif

1.//data_cond.wait(lk,[=](){return this->empty()});
        data_cond.wait(lk,[this]{return !queue_data.empty();});

二者效果一样

个人理解:使用data_cond.wait(lk,[=](){return this->empty()});会按值捕获更多的信息,但是我们并不需要,data_cond.wait(lk,[=](){return this->empty()});按引用捕获。

2.在try_pop()中锁外声明shared_ptr抛出异常的话可能出问题

3.return nullptr;//return shared_ptr()

二者效果一样,nullptr还省了构造。

thread_pool_easy.hpp:

//最简单的线程池::工作线程数目固定,当有任务要处理时就把他放进任务队列
//所以需要一个任务队列,用threadsafe_queue来实现
//各线程从任务队列中领取任务
//工作线程存储在vector容器中,并被引用到join_threads中,进行统一的析构管理
//

#ifndef _THREAD_POOL_EASY_HPP_
#define _THREAD_POOL_EASY_HPP_
#include "ThreadRAII.h" //join_threads
#include "threadsafe_queue.hpp"    //


#include 
#include 
#include 
#include 
#include 
class thread_pool
{
private:
	std::atomic_bool done;
	threadsafe_queue> work_queue;
	std::vector threads;
	join_threads joiner;
	void work_thread(){
		while(!done){
			std::function task;
			//std::shared_ptr> task;
			if(work_queue.try_pop(task)){
				task();
			}
			else{
				std::this_thread::yield();
			}
		}
	};
public:
	thread_pool():done(false),joiner(threads)
	{
		//auto thread_number = std::thread::hardware_concurrency(); 1.
		unsigned const thread_number = std::thread::hardware_concurrency();
		try{
			for(unsigned i =0; i
	void submit(Function F){
		//work_queue.push(F); 3.
		work_queue.push(std::function(F));
	}
};
#endif 

1.unsigned const thread_number = std::thread::hardware_concurrency();

thread_number类型是unsigned int

2.第一个参数是一个函数指针,指向类的目标成员函数;第二个参数需要给出相应的对象,以在它之上调用成员函数(这个参数可以是指向对象的指针,或对象本身,或有std::ref封装的对象)

3.//work_queue.push(F);
        work_queue.push(std::function(F));

在我的测试代码中,二者效果一样。

测试代码:

test_easy.cpp:

#include "threadsafe_queue.hpp"
#include "ThreadRAII.h"
#include "thread_pool_easy.hpp"
#include
#include

#include
std::mutex mut;
void Print(){
	std::lock_guard lk(mut);
	std::cout<(Print);
	}
	return 0;
}

Print里不加锁的话,多线程会抢占输出,输出会变杂乱。

接下来会实现一个能进行任务窃取的线程池。

你可能感兴趣的:(c++,C++并发编程,学习)