阻塞队列提升+线程池反思——c++线程库

✨阻塞队列+线程池——vstudio✨
🌟线程池实现——Linux🌟
🚀小线程池实现🚀
c++并发编程(书籍)

阻塞队列代码——管理任务
 ✨代码
  ✨Log.h
  ✨Task.h
  ✨BlockQueue.h
 🎭成员变量一览
 ✨反思总结
线程池——管理线程
 ✨结构介绍
 ✨代码
  ✨ThreadPool.h
 ✨反思总结
简化线程池
 ✨C++代码——thread库实现
 ✨C代码——pthread库实现
 ✨反思
🎭优秀文章参考
在这里插入图片描述

✨阻塞队列代码——管理任务

✨代码

Log.h
#pragma once

#include<iostream>
#include<mutex>
#include<thread>
using namespace std;
#include<stdio.h>
#include <direct.h> // Windows


enum
{
	SCREEN,ONEFILE,CLASSIFY
};
enum
{
	INFO = 10,DEBUG,MERROR,WARING,COUNT
};
class Log
{
	mutex mtx;
	Log() {}
public:
	static Log log;
	Log& operator=(const Log&) = delete;
	Log(const Log&) = delete;

	string GetFileName(int type)
	{
		string ret;
		switch (type)
		{
		case INFO:
			ret = "info.txt";
			break;
		case DEBUG:
			ret = "debug.txt";
			break;
		case ERROR:
			ret = "error.txt";
			break;
		case WARING:
			ret = "waring.txt";
			break;
		case COUNT:
			ret = "count.txt";
			break;
		}
		return ret;
	}
	string GetType(int type)
	{
		string ret;
		switch (type)
		{
		case INFO:
			ret = "[ info ]";
			break;
		case DEBUG:
			ret = "[ debug ]";
			break;
		case MERROR:
			ret = "[ error ]";
			break;
		case WARING:
			ret = "[ waring ]";
			break;
		case COUNT:
			ret = "[ count ]";
			break;
		}
		return ret;
	}
	void WriteToFile(int des,int type,const string& content)
	{
		string ret = GetType(type);
		ret += content;
		switch (des)
		{
		case SCREEN:
			cout << ret << endl;
			break;
		case CLASSIFY:
			string filename = GetFileName(type);

			{
				unique_lock<mutex> lg(mtx);
				FILE* fd = fopen(filename.c_str(), "a");
				char str[100];
				snprintf(str, sizeof(str), "%s\n", ret.c_str());
				fwrite(str, sizeof(char), strlen(str), fd);
				fclose(fd);
			}
			break;
		}
	}
};
Log Log::log;
Task.h
#pragma once

#include<iostream>
#include<unordered_map>
#include<functional>
#include<stdio.h>
#include<string>
#include<exception>
#include<thread>

using namespace std;
#include"Log.h"

using _fun_t = function<double()>;

class Task
{
public:
	// 引用捕获
	//Task(int a=0, int b=0, char op='+') : _a(a), _b(b), _op(op), _ret()
	//{
	//	_hash['+'] = [this] {
	//		_ret = (double)_a + _b; 
	//	};
	//	_hash['-'] = [this] {
	//		_ret = (double)_a - _b; 
	//	};
	//	_hash['*'] = [this] { 
	//		_ret = (double)_a * _b; 
	//	};
	//	_hash['/'] = [this] {
	//		if (_b == 0) _ret = 0;
	//		else _ret = (double)_a / _b;
	//	};
	//	_hash['%'] = [this] {
	//		if (_b == 0) _ret = 0;
	//		else _ret = _a % _b;
	//	};
	//}
	// 使用值捕获进行构造
	Task(int a = 0, int b = 0, char op = '+') : _a(a), _b(b), _op(op), _ret()
	{
		// 使用参数捕捉并且带有返回值就可以解决问题了
		// 现在就算是浅拷贝也可以
		_hash['+'] = [=]()->double {
			return (double)_a + _b;
		};
		_hash['-'] = [=]()->double {
			return(double)_a - _b;
		};
		_hash['*'] = [=]()->double {
			return (double)_a * _b;
		};
		_hash['/'] = [=]()->double {
			if (_b == 0) return 0;
			else return (double)_a / _b;
		};
		_hash['%'] = [=]()->double {
			if (_b == 0) return 0;
			else return _a % _b;
		};
	}

	void run()
	{
		_ret = _hash[_op]();
		
		Log::log.WriteToFile(CLASSIFY, INFO, "任务运行完成");
	}
	 
	string Print() const 
	{
		char ret[100];
		snprintf(ret,sizeof(ret), "%d %c %d = ?", _a, _op, _b);
		return ret;
	}
	string PrintResult()
	{
		char ret[100];
		snprintf(ret, sizeof(ret), "%d %c %d = %.3f", _a, _op, _b, _ret);
		return ret;
	}
private:
	int _a, _b;
	char _op;
	double _ret;
	unordered_map<char, function<double()>> _hash;
};
BlockQueue.h
#pragma once

#include<queue>
#include<vector>
#include<condition_variable>
#include<thread>
#include<mutex>
using namespace std;
#include"Task.h"


const int DEFAULT_TASK_NUMS = 5;
template<class T>
class BlockQueue
{
public:
	BlockQueue(int capacity = DEFAULT_TASK_NUMS)
		:_capacity(capacity)
	{
		Log::log.WriteToFile(CLASSIFY, INFO, "阻塞队列初始化完成");
	}
	bool empty()
	{
		unique_lock<mutex> lg(mtx);
		return _taskq.size() == 0;
	}
	bool full()
	{
		unique_lock<mutex> lg(mtx);
		return _taskq.size() == _capacity;
	}
	void push(const T& t)
	{
		unique_lock<mutex> lg(mtx);
		Log::log.WriteToFile(CLASSIFY, INFO, "开始放任务");
		string ret = t.Print();
		Log::log.WriteToFile(CLASSIFY, COUNT, ret );

		while(_taskq.size() == _capacity)
			_c_cond.wait(lg);
		
		//t.Print();
		_taskq.push(t);
		Log::log.WriteToFile(CLASSIFY, INFO, "放任务完成");

		Log::log.WriteToFile(CLASSIFY, INFO, "通知消费者");
		_p_cond.notify_one();
		Log::log.WriteToFile(CLASSIFY, INFO, "通知消费者完成");
	}
	void run()
	{
	//	unique_lock<mutex> lg(mtx);
	//	Log::log.WriteToFile(CLASSIFY, INFO, "开始计算任务");

	//	while (_taskq.size() == 0)
	//	{
	//		Log::log.WriteToFile(CLASSIFY, INFO, "消费者等待激活");
	//		_p_cond.wait(lg);
	//	}
	//	Log::log.WriteToFile(CLASSIFY, INFO, "消费者已激活");

		T& t = _taskq.front();
		_taskq.pop();// 使用引用接收,这个位置不能先释放,释放了就没有了


		T t = _taskq.front();
		_taskq.pop();

	//	Log::log.WriteToFile(CLASSIFY, INFO, "***生产者正在进行计算任务***");

	//	t.run();

	//	string ret = t.PrintResult();
	//	Log::log.WriteToFile(CLASSIFY, COUNT, ret);

	//	Log::log.WriteToFile(CLASSIFY, INFO, "***生产者计算任务完成***");
	//	
	//	Log::log.WriteToFile(CLASSIFY, INFO, "计算任务结束");

	//	Log::log.WriteToFile(CLASSIFY, INFO, "唤醒生产者");
	//	_c_cond.notify_one();
	}
	void pop(T* t)
	{
		unique_lock<mutex> lg(mtx);
		while (_taskq.size() == 0)
		{
			Log::log.WriteToFile(CLASSIFY, INFO, "消费者等待激活");
			_p_cond.wait(lg);
		}
		// 执行的是Task对象的浅拷贝
		*t = _taskq.front();// 这是个赋值,不是拷贝
		_taskq.pop();

		t->run();
		_c_cond.notify_one();
	}
private:
	queue<T> _taskq;
	int _capacity;
	mutex mtx;
	condition_variable _p_cond;// 生产者给消费者发信号
	condition_variable _c_cond;// 消费者给生产者发信号
};

🎭成员变量一览

Task.h

成员变量意义
_a成员一
_b成员二
_op运算符
_ret最终结果
unordered_map<char, function<double()>>运算符到运算函数的映射

BlockQueue.h

成员变量意义
queue _taskq;任务队列
int _capacity;任务队列的最大容量
mutex mtx;
condition_variable _p_cond;生产者给消费者发信号
condition_variable _c_cond;消费者给生产者发信号

✨反思总结

  1. 为什么要使用两个条件变量???
    如果只有一个条件变量
    当生产者和消费者同时访问_taskq,最开始没有任何任务,消费者被阻塞
    生产者生产任务,生产一个任务释放锁并且唤醒锁(自己唤醒自己)
    如果生产者锁和条件变量的竞争能力每次都强于消费者,当生产者生产满的时候,由于生产者的竞争能力强于消费者,那么消费者就不可能得到条件变量和锁,就会产生死锁
    两个条件变量
    当生产者生产一个就会通知消费者,生产者消费者等待的是不同的条件变量,就能比上面的要高效
    阻塞队列两把锁是为了更好的控制互斥和同步

  2. 为什么只有一把锁???
    他们访问的是同一个共享资源如果有两把锁,他们还是可能产生同时访问的问题
    一个锁为了控制互斥

  3. Log文件需不需要加锁???
    我们来分析Log输出的问题,在线程启动的时候,Log创建的文件属于共享资源,这个是必须要加锁的吧

  4. Task中lambda参数接收的问题——引用捕捉or传值捕捉
    引用捕捉
    原则上,引用捕捉,这种方式只能进行浅拷贝新的对象中存的还是之前的引用
    在这里插入图片描述
    传值捕捉
    传值捕捉又会面临一个问题,如何将计算出来的值进行保存呢???
    最终使用有返回值的方式进行捕捉
    并且我们现在就不需要担心什么深拷贝浅拷贝的问题了,只要他们的两个计算成员和运算符对,他的计算结果使用自己的结果接收就行了

  5. BlockQueue中拿任务——引用接收or赋值接收
    Task.h的run函数专门放出来两个代码段
    引用接收

T& t = _taskq.front();
_taskq.pop();// 使用引用接收,这个位置不能先释放,释放了就没有了

这个代码的问题在于使用引用接收之后立即释放原来的空间,这样就造成了内存访问异常

传值接收
传值就是拷贝,就能很好的避免内存访问冲突的问题
6. 一定要在最后回收线程

✨线程池——管理线程

✨结构介绍

请添加图片描述

一次性创建多个线程,将线程统一管理起来——将许多鱼放入鱼塘
将任务使用队列管理起来,每次生产任务都加入队列——将食物放到队列中
每次需要拿任务就从队列中拿,然后放到线程池中,让多个线程抢一个任务——鱼抢鱼食儿
没有任务的时候,所有线程等待;当有任务就唤醒——条件变量
注意
以上是形象上的理解,细节还是需要注意的,他们在抢食物的时候,需要先抢到锁,然后才能去拿食物,也就是说,这里的抢的是锁

✨代码

ThreadPool.h
#pragma once

#include<iostream>
#include<queue>
#include<vector>
#include<string>
#include<functional>
using namespace std;
#include"Task.h"
#include"Log.h"
#include"BlockQueue.h"

const int DEFAULT_THREADS = 5;
//const int DEFAULT_TASK_NUMS = 5;

// 让线程池去阻塞队列中取任务
template<class T>
class ThreadPool
{
	static ThreadPool<T> _threadpool;
	ThreadPool(int threads_capacity = DEFAULT_THREADS, int block_capacity = DEFAULT_TASK_NUMS)
		:_threads_capacity(threads_capacity),_blockq(block_capacity)
	{
		for (int i = 0; i < _threads_capacity; i++)
		{
			_threads.push_back(thread( bind(&ThreadPool<T>::run,this)));
		}
	}
	//ThreadPool()
	//	:_threads_capacity(DEFAULT_THREADS), _blockq(DEFAULT_TASK_NUMS)
	//{
	//	for (int i = 0; i < _threads_capacity; i++)
	//	{
	//		_threads.push_back(thread([this] {
	//			run();
	//			}));
	//	}
	//}
public:
	ThreadPool(const ThreadPool<T>& t) = delete;
	ThreadPool& operator=(const ThreadPool<T>& t) = delete;
	// 可以将析构函数私有,对自己这个类没有什么影响,但是在继承的时候就会发生错误
	~ThreadPool()
	{
		for (auto& th : _threads)
			th.join();
	}
	static ThreadPool<T>* GetInstance()
	{
		return &_threadpool;
	}

	void run()
	{
		while (1)
		{
			unique_lock<mutex> lg(_mutex);
			Log::log.WriteToFile(CLASSIFY, INFO, "线程开始启动");

			while (_blockq.empty())
				_p_cond.wait(lg);
			Log::log.WriteToFile(CLASSIFY, INFO, "线程获取任务");

			T t;
			_blockq.pop(&t);
			Log::log.WriteToFile(CLASSIFY, INFO, "运行任务");

			t.run();
			string ret = t.PrintResult();
			Log::log.WriteToFile(CLASSIFY, COUNT, ret);
			_c_cond.notify_one();
		}
	}
	void push(const T& t)
	{
		unique_lock<mutex> lg(_mutex);
		while (_blockq.full())
			_c_cond.wait(lg);
		_blockq.push(t);
		Log::log.WriteToFile(CLASSIFY, INFO, "线程池任务添加成功");
		_p_cond.notify_one();
	}
private:
	//queue<T> _taskq;// 阻塞队列
	BlockQueue<T> _blockq;
	vector<thread> _threads;
	int _threads_capacity;
	mutex _mutex;
	condition_variable _p_cond;// 消费者等待生产者
	condition_variable _c_cond;// 生产者等消费者
};

template<class T>
ThreadPool<T> ThreadPool<T>::_threadpool;

✨反思总结

  1. 静态成员函数可以访问类中的静态公有/私有成员变量
  2. 一定要在最后回收线程
  3. 线程池设计成单例模式
  4. 线程一定是在最开始的时候创建出来的,并且创建的线程要运行起来,需要注意函数绑定时this参数的问题

✨简化线程池

为了应对面试的手撕线程池,需要掌握一个简化版的线程池

✨C++代码——thread库实现

tinythreadpool.hpp

#include<iostream>
#include<mutex>
#include<thread>
#include<condition_variable>
#include<queue>
#include<vector>
#include<functional>
using namespace std;


class Task
{
public:
    void run(){}
private:
};

class ThreadPool
{
    ThreadPool(int thread_capacity = 5,int task_capacity = 5)
    :_thread_capacity(thread_capacity),_task_capacity(task_capacity)
    {
        puts("创建所有线程");
        for(int i=0;i<_thread_capacity;i++)
        {
            _threads.push_back(thread(bind(&ThreadPool::run,this)));
        }
    }
    ~ThreadPool()
    {
        for(auto& e:_threads)
        {
            e.join();
        }
    }
public:
    ThreadPool(const ThreadPool& tp)=delete;
    ThreadPool& operator=(const ThreadPool& tp) = delete;
    static ThreadPool& GetInstance()
    {
        static ThreadPool tp;
        return tp;
    }
    void run()
    {
        while(1)
        {
            unique_lock<mutex> lg(_mtx);
            while(_q_task.size() == 0)
                _p_cond.wait(lg);
            Task t = _q_task.front();
            _q_task.pop();
            t.run();
            puts("正在执行");
            _c_cond.notify_one();
        }
    }
    void push(const Task& t)
    {
        unique_lock<mutex> lg(_mtx);
        while(_q_task.size() == _task_capacity)
            _c_cond.wait(lg);
        _q_task.push(t);
        _p_cond.notify_one();
    }
private:
    queue<Task> _q_task; 
    vector<thread> _threads;
    int _thread_capacity;// 线程容量
    int _task_capacity;// 任务容量
    mutex _mtx;
    condition_variable _p_cond;// 生产者给消费者发消息
    condition_variable _c_cond;// 消费者给生产者发消息
};

main.cc

#include<iostream>
using namespace std;
#include"tinythreadpool.hpp"

int main()
{
    ThreadPool& tp = ThreadPool::GetInstance();
    srand(time(nullptr));
    while(1)
    {
        Task t;
        puts("生产任务");
        tp.push(t);
    }

    return 0;
}

✨C代码——pthread库实现

ThreadPool.h

#include<iostream>
#include<mutex>
#include<thread>
#include<condition_variable>
#include<queue>
#include<vector>
#include<functional>
using namespace std;
#include<pthread.h>


class Task
{
public:
    void run(){}
private:
};

class ThreadPool
{
    ThreadPool(int thread_capacity = 5,int task_capacity = 5)
    :_thread_capacity(thread_capacity),_task_capacity(task_capacity)
    {
        puts("创建所有线程");
        // for(int i=0;i<_thread_capacity;i++)
        // {
        //     _threads.push_back(thread(bind(&ThreadPool::run,this)));
        // }
        for(int i=0;i<_thread_capacity;i++)
        {
            pthread_t id;
            // c++11中不能使用bind作为pthread_create的回调函数
            // pthread_create(&id,nullptr,bind(&ThreadPool::run,this,placeholders::_1),nullptr);
            pthread_create(&id,nullptr,ThreadPool::run,this);
            
            _threads.push_back(id);
        }
    }
    // ~ThreadPool()
    // {
    //     for(auto& e:_threads)
    //     {
    //         e.join();
    //     }
    // }
    ~ThreadPool()
    {
        for(auto& e :_threads)
        {
            pthread_join(e,nullptr);
        }
    }
public:
    ThreadPool(const ThreadPool& tp)=delete;
    ThreadPool& operator=(const ThreadPool& tp) = delete;
    static ThreadPool& GetInstance()
    {
        // 懒汉模式
        // 第一次调用时初始化
        // c++11之后编译器保证值初始化一次
        static ThreadPool tp;
        return tp;
    }
    static void* run(void* args)
    {
        ThreadPool* tp = static_cast<ThreadPool*>(args);
        while(1)
        {
            pthread_mutex_lock(&tp->_mutex);
            while(tp->_q_task.size() == 0)
                pthread_cond_wait(&tp->_p_cond,&tp->_mutex);
            Task t = tp->_q_task.front();
            tp->_q_task.pop();
            t.run();
            puts("正在执行");
            pthread_cond_signal(&tp->_c_cond);
            pthread_mutex_unlock(&tp->_mutex);
        }
    }
    void push(const Task& t)
    {
        pthread_mutex_lock(&_mutex);
        while(_q_task.size() == _task_capacity)
            pthread_cond_wait(&_c_cond,&_mutex);
        _q_task.push(t);
        pthread_cond_signal(&_p_cond);
        pthread_mutex_unlock(&_mutex);
    }
    // void run()
    // {
    //     while(1)
    //     {
    //         unique_lock<mutex> lg(_mtx);
    //         while(_q_task.size() == 0)
    //             _p_cond.wait(lg);
    //         Task t = _q_task.front();
    //         _q_task.pop();
    //         t.run();
    //         puts("正在执行");
    //         _c_cond.notify_one();
    //     }
    // }
    // void push(const Task& t)
    // {
    //     unique_lock<mutex> lg(_mtx);
    //     while(_q_task.size() == _task_capacity)
    //         _c_cond.wait(lg);
    //     _q_task.push(t);
    //     _p_cond.notify_one();
    // }
private:
    queue<Task> _q_task;
    vector<pthread_t> _threads;
    int _thread_capacity;// 线程容量
    int _task_capacity;// 任务容量
    pthread_mutex_t _mutex = PTHREAD_MUTEX_INITIALIZER;
    pthread_cond_t _p_cond = PTHREAD_COND_INITIALIZER;
    pthread_cond_t _c_cond = PTHREAD_COND_INITIALIZER;
};

✨反思

  1. c++11后,懒汉模式不需要加锁,编译器只会初始化静态变量一次,保证了线程安全
    static ThreadPool& GetInstance()
    {
        // 懒汉模式
        // 第一次调用时初始化
        // c++11之后编译器保证值初始化一次
        static ThreadPool tp;
        return tp;
    }

  1. c++11规定,不能使用bind作为pthread_create的回调函数
    ThreadPool(int thread_capacity = 5,int task_capacity = 5)
    :_thread_capacity(thread_capacity),_task_capacity(task_capacity)
    {
        puts("创建所有线程");
        // for(int i=0;i<_thread_capacity;i++)
        // {
        //     _threads.push_back(thread(bind(&ThreadPool::run,this)));
        // }
        for(int i=0;i<_thread_capacity;i++)
        {
            pthread_t id;
            // c++11中不能使用bind作为pthread_create的回调函数
            // pthread_create(&id,nullptr,bind(&ThreadPool::run,this,placeholders::_1),nullptr);
            pthread_create(&id,nullptr,ThreadPool::run,this);
            
            _threads.push_back(id);
        }
    }

🎭优秀文章参考

线程池

  • 43
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 14
    评论
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值