c++11多线程与线程池

最近需要开发一个高性能计算库,涉及到c++多线程的应用,上次做类似的事情已经是4年多以前了,印象中还颇有些麻烦。悔当初做了就算了,也没想着留点记录什么的。这次又研究了一番,发现用上c++11特性之后,现在已经比较简单了,在此记录一下。

 

最简单的多线程情况,不涉及公共变量,各个线程之间独立运行,主线程只负责传入参数并接收运行结果。这种情况也是多线程性能最好的场景,因为不涉及锁的问题。之前c++标准库对多线程支持并不好,要么用boost线程库,要么用操作系统提供的线程自己轮。自从c++11之后,标准库对多线程支持好多了。下面给出一个简单的多线程示例代码。

#include <thread>
#include <future>
#include <vector>
#include <iostream>
using namespace std;

struct result {
	vector<double> x;
	double y;
};

void f1(promise<result> &res)
{
	vector<double> vec(2);
	vec[0] = 1.5;
	vec[1] = 2.2;
	res.set_value({ vec, 4.7 });
}

double f2(double x)
{
	return sin(x);
}

int main(int argc, char* argv[])
{
	promise<result> res1;
	packaged_task<double(double)> res2(f2);
	future<result> ft1 = res1.get_future();
	future<double> ft2 = res2.get_future();
	thread th1(f1, ref(res1));       // 线程参数均为copy,ref代表引用,必须显式给出
	thread th2(move(res2), 3.14/6);  // 为了提高性能采用move

	ft1.wait();
	ft2.wait();

	result r1 = ft1.get();
	double r2 = ft2.get();

	th1.join();  // 这两句对此例不必要,仅为了逻辑自洽给出
	th2.join();

	cout << "result: x = (" << r1.x[0] << ", " << r1.x[1] << "), y = " << r1.y << endl;
	cout << "sin(pi/6) = " << r2 << endl;
	return 0;
}

这段代码分别用了promise和packaged_task两种方式来新建线程并获得返回值。注意新建的线程通过复制参数来实现,因此对于引用的参数要用std::ref来声明,否则无法获得返回值。

 

对于大规模的计算,通常都把计算划分为小块,然后塞给新线程。小块计算的数量会很多,频繁创建和销毁线程的开销也很大,因此通常的做法是利用线程池。顾名思义,线程池中有若干线程,当有新任务来,就把任务分给线程运行,运行结束后该线程再等候下一个任务。下面给出线程池的实现,

ThreadPool.hpp

#pragma once
#ifndef THREAD_POOL_H
#define THREAD_POOL_H

#include <vector>
#include <queue>
#include <thread>
#include <atomic>
#include <condition_variable>
#include <future>

namespace ThreadPool
{
#define MAX_THREAD_NUM 8

	//线程池,可以提交变参函数或lambda表达式的匿名函数执行,可以获取执行返回值
	//支持类成员函数,支持类静态成员函数或全局函数,Operator()函数等
	class ThreadPool
	{
		typedef std::function<void()> Task;
	private:
		std::vector<std::thread> m_pool;     // 线程池
		std::queue<Task> m_tasks;    // 任务队列
		std::mutex m_lock;    // 同步锁
		std::condition_variable m_cv;   // 条件阻塞
		std::atomic<bool> m_isStoped;    // 是否关闭提交
		std::atomic<int> m_idleThreadNum;  //空闲线程数量
	public:
		ThreadPool(int size = MAX_THREAD_NUM) : m_isStoped(false)
		{
			size = size > MAX_THREAD_NUM ? MAX_THREAD_NUM : size;
			m_idleThreadNum = size;
			for (int i = 0; i < size; i++)
			{
				//初始化线程数量
				m_pool.emplace_back(&ThreadPool::scheduler, this);
			}
		}

		~ThreadPool()
		{
			Close();
			while (!m_tasks.empty()) {
				m_tasks.pop();
			}
			m_cv.notify_all();  // 唤醒所有线程执行
			for (std::thread& thread : m_pool) {
				if (thread.joinable()) {
					thread.join();  // 等待任务结束,前提是线程一定会执行完
				}
			}
			m_pool.clear();
		}

		// 打开线程池,重启任务提交
		void ReOpen() {
			if (m_isStoped) m_isStoped.store(false);
			m_cv.notify_all();
		}

		// 关闭线程池,停止提交新任务
		void Close() {
			if (!m_isStoped) m_isStoped.store(true);
		}

		// 判断线程池是否被关闭
		bool IsClosed() const {
			return m_isStoped.load();
		}

		// 获取当前任务队列中的任务数
		int GetTaskSize() { 
			return m_tasks.size(); 
		}

		// 获取当前空闲线程数
		int IdleCount() { 
			return m_idleThreadNum; 
		}

		// 提交任务并执行
		// 调用方式为 std::future<returnType> var = threadpool.Submit(...)
		// var.get() 会等待任务执行完,并获取返回值
		// 其中 ... 可以直接用函数名+函数参数代替,例如 threadpool.Submit(f, 0, 1)
		// 但如果要调用类成员函数,则最好用如下方式
		// threadpool.Submit(std::bind(&Class::Func, &classInstance)) 或
		// threadpool.Submit(std::mem_fn(&Class::Func), &classInstance)
		template<class F, class... Args>
		auto Submit(F&& f, Args&&... args)->std::future<decltype(f(args...))>
		{
			if (m_isStoped.load()) {
			  throw std::runtime_error("ThreadPool is closed, can not submit task.");
			}

			using RetType = decltype(f(args...));  // typename std::result_of<F(Args...)>::type, 函数 f 的返回值类型
			std::shared_ptr<std::packaged_task<RetType()>> task = std::make_shared<std::packaged_task<RetType()>>(
				std::bind(std::forward<F>(f), std::forward<Args>(args)...)
				);
			std::future<RetType> future = task->get_future();
			// 封装任务并添加到队列
			addTask([task](){
				(*task)(); 
			});

			return future;
		}
	private:
		// 消费者
		Task getTask() {
			std::unique_lock<std::mutex> lock(m_lock); // unique_lock 相比 lock_guard 的好处是:可以随时 unlock() 和 lock()
			while (m_tasks.empty() && !m_isStoped) {
				m_cv.wait(lock);
			}  // wait 直到有 task
			if (m_isStoped) {
				return Task();
			}
			assert(!m_tasks.empty());
			Task task = std::move(m_tasks.front()); // 取一个 task
			m_tasks.pop();
			m_cv.notify_one();
			return task;
		}

		// 生产者
		void addTask(Task task)
		{
			std::lock_guard<std::mutex> lock{ m_lock }; //对当前块的语句加锁, lock_guard 是 mutex 的 stack 封装类,构造的时候 lock(),析构的时候 unlock()
			m_tasks.push(task);
			m_cv.notify_one(); // 唤醒一个线程执行
		}

		// 工作线程主循环函数
		void scheduler()
		{
			while (!m_isStoped.load()) {
				// 获取一个待执行的 task
				Task task(getTask());
				if (task) {
					m_idleThreadNum--;
					task();
					m_idleThreadNum++;
				}
			}
		}
	};
}

#endif

 

该线程池的使用如下,

ThreadPool.cpp:

#include "ThreadPool.hpp"
#include <iostream>

struct gfun {
	int operator()(int n) {
		printf("%d  hello, gfun !  %d\n", n, std::this_thread::get_id());
		return 42;
	}
};

class Test
{
public:
	int GetThreadId(std::string a, double b)
	{
		std::this_thread::sleep_for(std::chrono::milliseconds(10000));
		std::thread::id i = std::this_thread::get_id();
		std::cout << "In Test, thread id: " << i << std::endl;
		std::cout << "a: " << a.c_str() << ", b = " << b << std::endl;
		return i.hash();
	}
};

int main()
{
	ThreadPool::ThreadPool worker{ 4 };
	Test t;
	std::cout << "at the beginning: " << std::endl;
	std::cout << "idle threads: " << worker.IdleCount() << std::endl;
	std::cout << "tasks: " << worker.GetTaskSize() << std::endl;
	std::future<int> f1 = worker.Submit(std::bind(&Test::GetThreadId, &t, "123", 456.789));

	std::cout << "after submit 1 task: " << std::endl;
	std::cout << "idle threads: " << worker.IdleCount() << std::endl;
	std::cout << "tasks: " << worker.GetTaskSize() << std::endl;
	std::future<int> f2 = worker.Submit(std::mem_fn(&Test::GetThreadId), &t, "789", 123.456);

	std::cout << "after submit 2 task: " << std::endl;
	std::cout << "idle threads: " << worker.IdleCount() << std::endl;
	std::cout << "tasks: " << worker.GetTaskSize() << std::endl;
	std::future<int> f3 = worker.Submit(gfun{}, 0);

	std::cout << "f1 = " << f1.get() << ", f2 = " << f2.get() << ", f3 = " << f3.get() << std::endl;

	std::cout << "after all task: " << std::endl;
	std::cout << "idle threads: " << worker.IdleCount() << std::endl;
	std::cout << "tasks: " << worker.GetTaskSize() << std::endl;
	return 0;
}

 

注意上面的例子中,线程池的线程数是一开始定义好的,此后也不会改变。但实际上,线程池中的线程数可以根据任务的多少动态调节。对于大规模计算的需求而言,cpu核数是固定的,要计算的任务数基本也是固定的,所以没必要这样做;如果是服务器处理网络请求,则可以采用线程数动态调节的方式,增加可扩展性。

 

完整的vs2013工程包可在如下地址下载

https://download.csdn.net/download/u014559935/10838079

转载于:https://my.oschina.net/propagator/blog/2985836

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值