之前实现过一个webserver其中的线程池实现方式让我感觉非常优雅就学了一手后来ai说也就小项目用用实际工程不是这样。所以记录一下线程池的不同实现方式顺便加深记忆。第一种要点拷贝构造和拷贝赋值需要delete因为线程不能被复制。移动虽然有时是可以的但是安全起见最好都给delete掉。这种方式直接把子线程detach不需要在主线程保存所有子线程但是主线程结束子线程拿不到主线程的数据就会出问题。所以这种情况主线程需要sleep或者放while(true)里。// ThreadPool.hclassThreadPool{public:explicitThreadPool(intthreadNum8);~ThreadPool();ThreadPool(constThreadPoolo)delete;ThreadPool(ThreadPoolo)delete;ThreadPooloperator(constThreadPoolo)delete;ThreadPooloperator(ThreadPoolo)delete;templatetypenameFvoidAddTask(Ftask);private:boolIsClosed();private:boolisClosed_false;std::queuestd::functionvoid()tasks_;std::mutex mtx_;std::condition_variable cond_;};templatetypenameFvoidThreadPool::AddTask(Ftask){if(IsClosed())return;std::lock_guardstd::mutexlocker(mtx_);tasks_.emplace(std::forwardF(task));cond_.notify_one();}// ThreadPool.cppThreadPool::ThreadPool(intthreadNum){assert(thread_num0);for(inti0;ithreadNum;i){std::thread([this](){std::unique_lockstd::mutexlocker(mtx_);while(true){if(!tasks_.empty()){autotaskstd::move(tasks_.front());// 使用move避免拷贝构造tasks_.pop();locker.unlock();task();locker.lock();}elseif(isClosed_){// 线程池关闭则结束子线程break;}else{// 任务队列为空则阻塞cond_.wait(locker);}}}).detach();}}ThreadPool::~ThreadPool(){{std::lock_guardstd::mutexlocker(mtx_);isClosed_true;}cond_.notify_all();}boolThreadPool::IsClosed(){std::lock_guardstd::mutexlocker(mtx_);returnisClosed_;}第二种要点使用packaged_task包裹函数可以通过get_future得到一个future对象该对象调用get来获取被包裹函数的返回值如果还有返回则阻塞。因为task是一个局部变量而需要在子线程中调用所以需要用shared_ptr管理其生命周期。这个实现用vector去管理工作线程所以在析构函数中需要join所有的线程。// ThreadPool.hclassThreadPool{public:explicitThreadPool(intthreadNum8);~ThreadPool();ThreadPool(constThreadPoolo)delete;ThreadPool(ThreadPoolo)delete;ThreadPooloperator(constThreadPoolo)delete;ThreadPooloperator(ThreadPoolo)delete;templatetypenameF,typename...ArgsautoAddTask(Ftask,Args...args)-std::futuredecltype(task(args...));private:voidWorkerLoop();private:boolisClosed_false;std::queuestd::functionvoid()tasks_;std::vectorstd::threadworkers_;std::mutex mtx_;std::condition_variable cond_;};templatetypenameF,typename...ArgsautoThreadPool::AddTask(Ftask,Args...args)-std::futuredecltype(task(args...)){usingReturnTypedecltype(task(args...));// 用decltype编译时确定任务函数返回值类型autotaskstd::make_sharedstd::packaged_taskReturnType()(std::bind(std::forwardF(task),std::forwardArgs(args)...));autofuturetask-get_future();// 返回类型为std::futureReturnType{std::lock_guardstd::mutexlocker(mtx_);if(isClosed_){// 线程池关闭不能再加任务返回无效future或者throw异常returnstd::futureReturnType();}tasks_.emplace([task]{(*task)();});}cond_.notify_one();// 插入完后记得唤醒一个工作线程returnfuture;}// ThreadPool.cppThreadPool::ThreadPool(intthreadNum){assert(threadNum0);for(inti0;ithreadNum;i){workers_.emplace_back(ThreadPool::WorkerLoop,this);}}ThreadPool::~ThreadPool(){{std::lock_guardstd::mutexlocker(mtx_);isClosed_true;}cond_.notify_all();for(autoworker:workers_){if(worker.joinable()){worker.join();}}}voidThreadPool::WorkerLoop(){std::unique_lockstd::mutexlocker(mtx_);while(true){// 线程池关闭任务队列中有任务时不再阻塞其他线程唤醒。cond_.wait(locker,[this]{returnisClosed_||!tasks_.empty();});if(isClosed_tasks_.empty())break;// 所有任务处理完后才退出autotaskstd::move(tasks_.front());tasks_.pop();locker.unlock();task();locker.lock();}}第三种使用无锁队列实现无锁线程池避免锁竞争。其他线程池应该分配多少线程今天面试完被压力懵了连线程池应该分配多少线程都忘了直接前言不搭后语左右脑互搏。首先需要知道自己电脑的cpu核心数使用std::thread::hardware_concurrency();获取核心数。接下来就是分情况计算线程数了。cpu密集型任务cpu密集型任务基本不会阻塞每时每刻都在使用cpu核心。这种情况下需要避免上下文的切换所以不要分配过多的线程分配过多的线程当超过物理核心数时操作系统就会让多个线程在同一核心上并发运行这时会有上下文切换。而且线程运行时会访问该核心的L1/L2缓存当多个线程同时使用时会污染缓存导致未命中去从内存从新读取。所以cpu密集型通常分配等于cpu核心数的线程数。核心数1的情况 线程可能出现内存页错误或其他原因阻塞线程这时多一个线程可以利用这个空闲cpu时间。核心数-1的情况留出一个核心用于处理系统的任务和其他服务其实如果是轻量级的cpu任务任务切换开销小多线程能更好利用 CPU 。线程数可以为核心数的2倍IO密集型任务IO密集型任务会频繁的阻塞基本大部分时间都花在了阻塞上。如果所有的线程都阻塞当有新的任务可以执行时此时cpu空闲却没有线程执行。所以IO密集型任务需要多分配点线程IO密集型通常分配cpu核心数的2~5倍更具体的计算线程数核心数×(1IO阻塞时间cpu执行时间)线程数核心数\times(1\frac{IO阻塞时间}{cpu执行时间})线程数核心数×(1cpu执行时间IO阻塞时间​)混合型任务直接进行压测吧。或者动态调整完…