C++11与简单线程池实现

 C++11与简单线程池实现

膜拜了一篇github上高赞的线程池实现,其中用到了很多C++11的新特性。

不足百行的线程池代码,里面有很多值得学习、借鉴的思想和方法 (@_@~好学脸)

class noncopyable
{
public:
	noncopyable(){}
	~noncopyable(){}

private:
	noncopyable(const noncopyable&);
	const noncopyable& operator= (noncopyable&){}
};

定义一不可拷贝的类,将该类拷贝构造函数与赋值运算符的重载声明成私有。

threadPool类继承noncopyable

class threadpool : public noncopyable

首先是需要用到的成员变量

    using Task = function<void()>; //using对标typedef比其功能多一点 它可用于模板   
	queue<Task> _tasks;            //任务队列,先入队列的先执行(此数据结构 应结合应用场景)
	vector<thread> _pool;          //线程池

	mutex _mutex;                  //互斥量   (同步)
	condition_variable task_cv;    //条件变量 (条件阻塞)

	atomic_bool _isrun{true};      //原子化的bool (线程池是否执行)
	atomic_int _curFreeThreadNum;  //原子化的int  (空闲的线程数)

threadPool的构造函数中初始化线程池

    #define MAX_THREAD_POOL_NUM 16  //池中最大线程数
    
    threadpool(unsigned short size) //需要起多少线程
    {
	    addthreadpool(size);
    }

	void addthreadpool(unsigned short size)
	{
		for (;size < MAX_THREAD_POOL_NUM && size > 0; --size)
		{
			_pool.emplace_back([this] //  (1)emplace_back与push_back 见备注
				{
					while (_isrun)    //运行中一直循环
					{
						Task task;

						{
							unique_lock<mutex> lock(_mutex);
							task_cv.wait(lock, [this] // (2)条件变量condition_variable
								{
									return !_isrun || !_tasks.empty();
								}); // wait 直到有 task

							if (_tasks.empty() && !_isrun) //满足该条件退出
							    return;

							task = move(_tasks.front()); //去除任务队列中前面的任务
							_tasks.pop(); //出队列
						}

						_curFreeThreadNum++;
						task(); //真正执行
						_curFreeThreadNum--;
					}
				});
		}
	}

commit模板函数作用是将需要执行的方法push到任务队列中

    template<class F, class... Args>
	auto commit(F&& f, Args&&... args) ->future<decltype(f(args...))> (4)模板函数签名
	{
		if (!_run)    // stoped ??
			throw runtime_error("commit on ThreadPool is stopped.");

		using RetType = decltype(f(args...)); // typename std::result_of<F(Args...)>::type, 函数 f 的返回值类型
		auto task = make_shared<packaged_task<RetType()>>(
			bind(forward<F>(f), forward<Args>(args)...) (5)packaged_task与forward
		); // 把函数入口及参数,打包(绑定)
		future<RetType> future = task->get_future();
		{    // 添加任务到队列
			lock_guard<mutex> lock{ _lock };//对当前块的语句加锁  lock_guard 是 mutex 的 stack 封装类,构造的时候 lock(),析构的时候 unlock()
			_tasks.emplace([task](){ // push(Task{...}) 放到队列后面
				(*task)();
			});
		}
#ifdef THREADPOOL_AUTO_GROW
		if (_idlThrNum < 1 && _pool.size() < THREADPOOL_MAX_NUM)
			addThread(1);
#endif // !THREADPOOL_AUTO_GROW
		_task_cv.notify_one(); // 唤醒一个线程执行

		return future;
	}

线程池的析构

    ~threadpool()
	{
		_run=false;
		_task_cv.notify_all(); // 唤醒所有线程执行
		for (thread& thread : _pool) {
			//thread.detach(); // 让线程“自生自灭”
			if(thread.joinable())
				thread.join(); // 等待任务结束, 前提:线程一定会执行完
		}
	}

结合调用总结下大体流程

threadpool executor{ 10 };
std::future<std::string> fh = executor.commit([]()->
                                std::string { std::cout << "hello, fh !  " <<                             
                                std::this_thread::get_id() << 
                                std::endl; return "hello,fh ret !"; });

std::cout << fh.get().c_str() << "  " 
          << std::this_thread::get_id() << std::endl;

还是比较清晰的,声明并初始化线程池,首先进入构造函数,添加特定数量线程,每个线程都会在_task_cv.wait时阻塞,并释放锁定(对mutex的);随后调用commit方法:将任务放入packaged_task打包,并塞进任务队列。_task_cv.notify_one()唤醒一个线程。这时queue<Task> _tasks已不为空,线程又被唤醒,因此任务会在一wait线程种继续执行(从队列中取出)。

最后离开作用域,析构被调用;_run=false 线程一定会被唤醒_task_cv.notify_all() 唤醒所有用此条件变量阻塞的线程thread.join() 等待任务结束 END


备注

(1)emplace_back与push_back

 emplace_back() 和 push_back() 的区别,就在于底层实现的机制不同。push_back() 向容器尾部添加元素时,首先会创建这个元素,然后再将这个元素拷贝或者移动到容器中(如果是拷贝的话,事后会自行销毁先前创建的这个元素);而 emplace_back() 在实现时,则是直接在容器尾部创建这个元素,省去了拷贝或移动元素的过程。

(2)条件变量condition_variable

std::condition_variable 是条件变量,更多有关条件变量的定义参考维基百科。Linux 下使用 Pthread 库中的 pthread_cond_*() 函数提供了与条件变量相关的功能, Windows 则参考 MSDN

当 std::condition_variable 对象的某个 wait 函数被调用的时候,它使当前线程进入睡眠状态并自动释放锁。当前线程会一直被阻塞,直到另外一个线程在相同的 std::condition_variable 对象上调用了 notification 函数来唤醒当前线程。线程唤醒后,它重新获取线程进入睡眠状态时释放的锁定~

std::condition_variable 对象通常使用 std::unique_lock<std::mutex> 来等待,如果需要使用另外的 lockable 类型,可以使用 std::condition_variable_any 类,本文后面会讲到 std::condition_variable_any 的用法。

本线程池实现中 使用了其中的一个wait函数,当匿名函数返回false时 才会有等待的可能

    template <class _Predicate>
    void wait(unique_lock<mutex>& _Lck, _Predicate _Pred) { // wait for signal and test predicate
        while (!_Pred()) { //(3)虚假唤醒
            wait(_Lck);
        }
    }

(3)虚假唤醒

源码中有while(!_Pred())已经杜绝了虚假唤醒(spurious wakeup)

即使没有线程调用condition_signal, 原先调用condition_wait的函数也可能会返回。此时线程被唤醒了,但是条件并不满足,这个时候如果不对条件进行检查而往下执行,就可能会导致后续的处理出现错误。 

(4)模板函数签名

该模板表明可以传入多形参的方法,“->”返回值后置(放在前面并不知是何类型),decltype自动推导返回值类型。这样写模板简直骚气到不行

(5)packaged_task与forward

std::packaged_task 包装一个可调用的对象,并且允许异步获取该可调用对象产生的结果。包装对象的执行结果传递给了std::future对象。本例中task是异步执行的,不会立刻执行

std::forward 实现了完美转发即左右值属性强制不发生变化


文章参考: http://c.biancheng.net/view/6826.html

                   https://www.cnblogs.com/haippy/p/3284540.html

 

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值