【发布时间】:2021-08-12 00:56:15
【问题描述】:
我在 github 上阅读了一些线程池的代码。
这是一个很好的,我修改过的。
#pragma once
#include <vector>
#include <queue>
#include <memory>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <future>
#include <functional>
#include <stdexcept>
class ThreadPool {
public:
ThreadPool(size_t);
template<class F, class... Args>
auto enqueue(F&& f, Args&&... args) -> std::future<typename std::result_of<F(Args...)>::type>; // -> apparently define return type is future
~ThreadPool();
private:
// need to keep track of threads so we can join them
std::vector< std::thread > workers;
// the task queue
std::queue< std::function<void()> > tasks;
// synchronization
std::mutex queue_mutex;
std::condition_variable condition;
bool stop;
};
// the constructor just launches some amount of workers
inline ThreadPool::ThreadPool(size_t threads) : stop(false) {
for(size_t i = 0;i<threads;++i)
workers.emplace_back( // thread variable, emplace back just pass in a function for thread to construct
[this] {
for(;;) {
std::function<void()> task;
{
std::unique_lock<std::mutex> lock(this->queue_mutex);
this->condition.wait(lock, [this] { return this->stop || !this->tasks.empty(); });
// wait stop true or task not empty
if(this->stop && this->tasks.empty()) return; // only on stop and empty task, perfect exit
// if only stop, still will hanle left task
task = std::move(this->tasks.front()); // get the first task
this->tasks.pop();
}
task();
}
});
}
// add new work item to the pool
template<class F, class... Args>
auto ThreadPool::enqueue(F&& f, Args&&... args) -> std::future<typename std::result_of<F(Args...)>::type> {
using return_type = typename std::result_of<F(Args...)>::type;
auto task = std::make_shared< std::packaged_task<return_type()> >(std::bind(std::forward<F>(f), std::forward<Args>(args)...));
std::future<return_type> res = task->get_future();
{
std::unique_lock<std::mutex> lock(queue_mutex);
// don't allow enqueueing after stopping the pool
if(stop) throw std::runtime_error("enqueue on stopped ThreadPool");
tasks.emplace([task](){ (*task)(); });
}
condition.notify_one();
return res;
}
// the destructor joins all threads
inline ThreadPool::~ThreadPool() {
{
std::unique_lock<std::mutex> lock(queue_mutex);
stop = true;
}
condition.notify_all();
for(std::thread &worker: workers) worker.join();
}
我用它来加速我的代码。例如,我有 1000 个独立任务,每个任务花费大约 1 秒。
我发现使用这个线程池,它比一个循环慢。
所以,我写了一些小代码来检查。
这是我的代码。
#include "./time_util.h" // this is a timer class, just use to get time
#include "./thread_pool.hpp" // this is the class Threadpool header
size_t i = 0;
void test() {
// printf("hah\n");
i++;
}
#define N 1000000
void fun1() {
printf("func1\n");
ThreadPool pool(10);
for (int i = 0; i < N; ++i) {
pool.enqueue(test);
}
}
void fun2() {
printf("func2\n");
for (int i = 0; i < N; ++i) {
test();
}
}
int main(int argc, char** argv) {
util::time_util::Timer t;
t.StartTimer();
if (argc == 1) {
fun1();
} else {
fun2();
}
printf("i=%d\n", i);
t.EndTimer("1");
}
循环版本花费 0.005 秒 线程池花费 5s。
我的机器cpu是2.2G 24core(48thread),centos7。
而且,线程池的结果不正确(没关系,因为我没有加锁)
你能解释一下吗?这是否意味着对于小任务,我不能使用线程池来加速?
【问题讨论】:
-
因为每个线程中完成的工作量非常小,而调度的开销要大得多。在 threadproc 中做一些实际的工作。
i++;也是 UB,因此使所有测试无效。 -
如果我的独立任务每个只有 1 秒,这是否意味着我无法使用线程池加速? (假设我不能将 10task 绑定为一个)@RichardCritten
-
与操作系统必须进行上下文切换的所有线程一样,每个线程的工作量需要比上下文切换时间加上线程池本身的开销大 1 个或多个数量级。线程池的理想用例是磁盘或网络 IO。
-
M 的经验表明,为了充分发挥线程池的优势,在线程池中要做的最小单元工作实际上是相当高的。以解压 512x512 JPEG 图像的时间顺序为例。
-
如果我想加快我的小任务组,有什么好的方法吗? (500 个任务,循环运行成本 80 秒。)@RichardCritten
标签: c++ multithreading stl threadpool