Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

threadpool c++ implementation questions

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.
like image 920
amigo421 Avatar asked Apr 13 '18 06:04

amigo421


People also ask

How do you implement a ThreadPool?

The thread pool implementation consists of two parts. A ThreadPool class which is the public interface to the thread pool, and a PoolThread class which implements the threads that execute the tasks. To execute a task the method ThreadPool. execute(Runnable r) is called with a Runnable implementation as parameter.

How many maximum threads can be created using a ThreadPool?

ThreadPool will create maximum of 10 threads to process 10 requests at a time. After process completion of any single Thread, ThreadPool will internally allocate the 11th request to this Thread and will keep on doing the same to all the remaining requests.

When should you not use ThreadPool?

Thread pools do not make sense when you need thread which perform entirely dissimilar and unrelated actions, which cannot be considered "jobs"; e.g., One thread for GUI event handling, another for backend processing. Thread pools also don't make sense when processing forms a pipeline.

What is the difference between thread and ThreadPool?

A thread pool is - as the name suggests - a pool of worker threads which are always running. Those threads then normally take tasks from a list, execute them, then try to take the next task. If there's no task, the thread will wait.


1 Answers

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.

like image 121
Mike van Dyke Avatar answered Nov 01 '22 06:11

Mike van Dyke