一个线程池的例子
前几天在B站上看到一个线程池的例子,用到了很多不熟的语法,于是把它记录下来,方便后续慢慢研究。
在windows下用vs2017编译测试过,C++14和C++17都能跑通。
直接上代码:
ThreadPool.hpp:
#ifndef __MY_THREAD_POOL_INCLUDE__ #define __MY_THREAD_POOL_INCLUDE__ #include <vector> #include <mutex> #include <thread> #include <chrono> #include <queue> #include <condition_variable> #include <future> #include <memory> namespace MyCommSpace { class ThreadPool { public: ThreadPool(int threadNum); // 添加任务到队列中. 注意: -> 表示返回值类型后置。 template<typename F, typename ...Arg> auto enques(F&& f, Arg&& ... arg)->std::future<typename std::result_of<F(Arg...)>::type>; ~ThreadPool(); private: void worker(); // 线程的执行内容 bool isstop = false; // true表示线程池停止 std::condition_variable cv; std::mutex mtx; std::vector<std::thread> workers; // 线程集合 std::queue<std::function<void()>> myque; // 任务队列 }; ThreadPool::ThreadPool(int threadNum) { printf("ThreadPool 创建线程池,线程数量为:%d\n", threadNum); for (auto i = 0; i < threadNum; i++) { workers.emplace_back([this]() { this->worker(); }); } } ThreadPool::~ThreadPool() { printf("~ThreadPool 被析构 ++++++ \n"); { std::unique_lock<std::mutex> mmm(mtx); isstop = true; } cv.notify_all(); // 通知所有阻塞中的线程 for (auto& one : workers) // 等待所有线程执行完毕 { one.join(); } printf("~ThreadPool 被析构 ------ \n"); } /* 插入任务队列: 可以理解f是任务执行的函数 arg是传递进来的参数 返回值是: std::future<typename std::result_of<F(Arg...)>::type> 可以由f指定。 */ template<typename F, typename ...Arg> auto ThreadPool::enques(F&& f, Arg&& ... arg) -> std::future<typename std::result_of<F(Arg...)>::type> { // 获得f执行后的类型 using functype = typename std::result_of<F(Arg...)>::type; // 获得一个智能指针,指向一个呗包装为 functype()的task auto task = std::make_shared<std::packaged_task<functype()>>( std::bind(std::forward<F>(f), std::forward<Arg>(arg)...) ); // 获得future std::future<functype> rsfuture = task->get_future(); { std::lock_guard<std::mutex> lll(mtx); if (isstop) throw std::runtime_error("线程池已停止......"); // 添加任务到队列中 myque.emplace([task]() { (*task)(); }); } // 通知线程去执行任务 cv.notify_one(); return rsfuture; } void ThreadPool::worker() { while (1) { std::function<void()> task; // 定义任务 { // 从队列中取得一个任务 std::unique_lock<std::mutex> lock(mtx); cv.wait(lock, [this] {return this->isstop || !this->myque.empty(); }); // 注意:实际应用时没必要在 myque.empty() 时返回,后续可以继续添加任务。 if (isstop && myque.empty()) return; task = std::move(this->myque.front()); this->myque.pop(); } // 执行任务 task(); } } } #endif // __MY_THREAD_POOL_INCLUDE__
测试:
main.cpp
#include "ThreadPool.hpp" int GetSum(int a, int b) { std::cout << "GetSum. ThreadID:" << std::this_thread::get_id() << std::endl; return a + b; } void threadTest() { MyCommSpace::ThreadPool mypool(4); for (auto i = 0; i < 30; i++) { #if 1 auto rsfuture0 = mypool.enques([](int a, int b) ->int { std::cout << "ThreadID:" << std::this_thread::get_id() << std::endl; return a + b; }, 10 * i, 10 * i); std::cout << "thread rs:" << rsfuture0.get() << std::endl; #else // 上述lamda表达式可以写成下面这样,便于理解。 auto rsfuture0 = mypool.enques(&GetSum, 10 * i, 10 * i); std::cout << "thread rs:" << rsfuture0.get() << std::endl; #endif } } int main() { threadTest(); return 0; }
结果:
可以看到只有4个线程在跑
总结:
github上面的一个线程池:
https://github.com/mtrebi/thread-pool/blob/master/include/ThreadPool.h
ThreadPool.h:
#pragma once #include <functional> #include <future> #include <mutex> #include <queue> #include <thread> #include <utility> #include <vector> #include "SafeQueue.h" class ThreadPool { private: class ThreadWorker { private: int m_id; ThreadPool * m_pool; public: ThreadWorker(ThreadPool * pool, const int id) : m_pool(pool), m_id(id) { } void operator()() { std::function<void()> func; bool dequeued; while (!m_pool->m_shutdown) { { std::unique_lock<std::mutex> lock(m_pool->m_conditional_mutex); if (m_pool->m_queue.empty()) { m_pool->m_conditional_lock.wait(lock); } dequeued = m_pool->m_queue.dequeue(func); } if (dequeued) { func(); } } } }; bool m_shutdown; SafeQueue<std::function<void()>> m_queue; std::vector<std::thread> m_threads; std::mutex m_conditional_mutex; std::condition_variable m_conditional_lock; public: ThreadPool(const int n_threads) : m_threads(std::vector<std::thread>(n_threads)), m_shutdown(false) { } ThreadPool(const ThreadPool &) = delete; ThreadPool(ThreadPool &&) = delete; ThreadPool & operator=(const ThreadPool &) = delete; ThreadPool & operator=(ThreadPool &&) = delete; // Inits thread pool void init() { for (int i = 0; i < m_threads.size(); ++i) { m_threads[i] = std::thread(ThreadWorker(this, i)); } } // Waits until threads finish their current task and shutdowns the pool void shutdown() { m_shutdown = true; m_conditional_lock.notify_all(); for (int i = 0; i < m_threads.size(); ++i) { if(m_threads[i].joinable()) { m_threads[i].join(); } } } // Submit a function to be executed asynchronously by the pool template<typename F, typename...Args> auto submit(F&& f, Args&&... args) -> std::future<decltype(f(args...))> { // Create a function with bounded parameters ready to execute std::function<decltype(f(args...))()> func = std::bind(std::forward<F>(f), std::forward<Args>(args)...); // Encapsulate it into a shared ptr in order to be able to copy construct / assign auto task_ptr = std::make_shared<std::packaged_task<decltype(f(args...))()>>(func); // Wrap packaged task into void function std::function<void()> wrapper_func = [task_ptr]() { (*task_ptr)(); }; // Enqueue generic wrapper function m_queue.enqueue(wrapper_func); // Wake up one thread if its waiting m_conditional_lock.notify_one(); // Return future from promise return task_ptr->get_future(); } };
SafeQueue.h
#pragma once #include <mutex> #include <queue> // Thread safe implementation of a Queue using an std::queue template <typename T> class SafeQueue { private: std::queue<T> m_queue; std::mutex m_mutex; public: SafeQueue() { } SafeQueue(SafeQueue& other) { //TODO: } ~SafeQueue() { } bool empty() { std::unique_lock<std::mutex> lock(m_mutex); return m_queue.empty(); } int size() { std::unique_lock<std::mutex> lock(m_mutex); return m_queue.size(); } void enqueue(T& t) { std::unique_lock<std::mutex> lock(m_mutex); m_queue.push(t); } bool dequeue(T& t) { std::unique_lock<std::mutex> lock(m_mutex); if (m_queue.empty()) { return false; } t = std::move(m_queue.front()); m_queue.pop(); return true; } };