Tyler
Tyler

Reputation: 1837

Thread pool for interdependent tasks

I have a problem that is well served by a thread pool in the nonrecursive case and it would benefit greatly from tasks (the work/function given to the pool) being able to add more tasks to the pool. The problem with my thread pool implementation is that the first level of tasks fills all the worker threads, creates the second level of tasks, then blocks while it waits for the second level tasks to finish. Since all worker threads are blocked waiting on the second level to finish, the second level tasks never get executed, thus the whole program deadlocks.

Are there any general solutions to this? Possibly a preemptive thread pool (if that's even possible). I did think of having tasks be explicitly prioritized, but the problem with that is that it doesn't automatically handle dependencies; it requires more work on the API user.

Thanks in advance for any insight or suggestions.

EDIT: thread pool class def

class{
public:
    thread_pool() = delete;
    thread_pool(const thread_pool&) = delete;
    thread_pool(unsigned int threads);
    ~thread_pool();

    template<class T, class... Args>
    std::future<T>
    async(std::function<T(Args...)>&& f, Args&&... args);

    template<class... Args>
    std::future<void>
    async(std::function<void(Args...)>&& f, Args&&... args);

    template<class T>
    std::future<T>
    async(std::function<T()>&& f);

    std::future<void>
    async(std::function<void()>&& f);

protected:
    void init_threads();
    void join_threads();
};

Upvotes: 0

Views: 992

Answers (1)

Mihai Stan
Mihai Stan

Reputation: 1052

You are using a fixed number of threads to prevent the case of too many active tasks at the same time, but when a first-level task waits for a second-level one, that thread is no longer active, so it should no longer count towards the fixed number of running threads.

The way i see it you have two ways of working around this:

  1. Mark the thread as busy when waiting for another task and tells the thread pool that it can temporarily create a new thread to replace it. (this is similar to the Windows Thread Pool CallbackMayRunLong function).

  2. Use a task done callback to resume the first-level task after the second-level one(s) finish instead of waiting for them. (similar to how you would use tasks in javascript).

Although more complex, the second option is much more flexible and std::bind gives you a few options for preserving state between those callbacks

Upvotes: 1

Related Questions