threadpool c++ implementation questions

2020-07-13 08:38发布

here and here , we can see similar threadpool implementations.

my question is about function to add the task to threadpool, these are add and enqueue in projects above respectively.

because these look very similar I'm posting a piece of one here (from second project)

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;
}

container tasks declared as :

std::queue< std::function<void()> > tasks;

so my questions are:

  1. why tasks declared with additional wrapper std::function around task variable ? why a queue of the tasks is not declared as a container of std::packaged_task which is also a callable object? I suppose that queue of the tasks should contains "universal" callable objects without parameters and without return type. so the removing parameters achieved by the binding, and extra wrapper std::function assists to remove return type, is it correct or no? also about the using of shared_ptr - is it only to avoid a collision that packaged_task is movable type but std::function is a copyable ?
  2. is it a good practice to use one shared task queue for all threads ? I'm looking at Anthony Williams "C++ Concurrency in action" he recommends to avoid this to prevent cache line contention. and he recommends to use more advanced technique with two levels of queue - global and thread_local for worker's threads.

1条回答
你好瞎i
2楼-- · 2020-07-13 09:37

In case you are still looking for an answer (you mostly answered it on your own):

  1. Exactly as you supposed.
  2. It's not "not good practice". It just leads to a performance drop, not only due to cache line contention, but also because of contention on the task list.

Current HPC thread pool implementations mostly use a work stealing scheduler: Each worker has its own queue. The worker pulls and pushes work from/to his own queue, until he has finished all the tasks in its own queue. Then it steals tasks from other workers and executes those.

查看更多
登录 后发表回答