线程池C++实现问题
threadpool c++ implementation questions
here and here ,我们可以看到类似的线程池实现。
我的问题是关于将任务添加到线程池的函数,在上面的项目中分别是add和enqueue。
因为这些看起来非常相似,所以我在这里张贴其中的一张(来自第二个项目)
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;
}
容器任务声明为:
std::queue< std::function<void()> > tasks;
所以我的问题是:
- 为什么在 task 变量周围用额外的包装器 std::function 声明任务?为什么
任务队列未声明为容器
std::packaged_task 哪个也是可调用对象?我想
任务队列应包含 "universal" 个可调用对象
没有参数,也没有 return 类型。所以删除
通过绑定实现的参数和额外的包装器 std::function
协助删除 return 类型,是否正确?还有关于 shared_ptr 的使用 - packaged_task 是可移动类型而 std::function 是可复制类型只是为了避免冲突吗?
- 为所有线程使用一个共享任务队列是一种好习惯吗?
我在看安东尼·威廉姆斯 "C++ Concurrency in action" 他
建议避免这种情况以防止缓存行争用。和他
建议使用具有两级队列的更高级技术 -
全局和 thread_local 用于工作线程。
如果您仍在寻找答案(您大部分是自己回答的):
- 和你想象的一样。
- 不是 "not good practice"。它只会导致性能下降,不仅是由于缓存行争用,还因为任务列表上的争用。
当前的 HPC 线程池实现大多使用工作窃取调度程序:每个工作线程都有自己的队列。 worker pull 和 push 工作 from/to 他自己的队列,直到他完成了自己队列中的所有任务。然后它从其他工作人员那里窃取任务并执行这些任务。
here and here ,我们可以看到类似的线程池实现。
我的问题是关于将任务添加到线程池的函数,在上面的项目中分别是add和enqueue。
因为这些看起来非常相似,所以我在这里张贴其中的一张(来自第二个项目)
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;
}
容器任务声明为:
std::queue< std::function<void()> > tasks;
所以我的问题是:
- 为什么在 task 变量周围用额外的包装器 std::function 声明任务?为什么 任务队列未声明为容器 std::packaged_task 哪个也是可调用对象?我想 任务队列应包含 "universal" 个可调用对象 没有参数,也没有 return 类型。所以删除 通过绑定实现的参数和额外的包装器 std::function 协助删除 return 类型,是否正确?还有关于 shared_ptr 的使用 - packaged_task 是可移动类型而 std::function 是可复制类型只是为了避免冲突吗?
- 为所有线程使用一个共享任务队列是一种好习惯吗? 我在看安东尼·威廉姆斯 "C++ Concurrency in action" 他 建议避免这种情况以防止缓存行争用。和他 建议使用具有两级队列的更高级技术 - 全局和 thread_local 用于工作线程。
如果您仍在寻找答案(您大部分是自己回答的):
- 和你想象的一样。
- 不是 "not good practice"。它只会导致性能下降,不仅是由于缓存行争用,还因为任务列表上的争用。
当前的 HPC 线程池实现大多使用工作窃取调度程序:每个工作线程都有自己的队列。 worker pull 和 push 工作 from/to 他自己的队列,直到他完成了自己队列中的所有任务。然后它从其他工作人员那里窃取任务并执行这些任务。