1

I have copied the below threadpool implementation from https://pastebin.com/MM5kSvH6. All looks good but i can't understand the logic at Line number 32 and Line number 71. Aren't both these lines trying to execute the function? I thought in threadpool, the thread is supposed to pull task from task queue and then execute it? In that sense line 71 looks OK but i am confused by line 32. Instead of adding the task to the queue why is it trying to execute the same?

#include <condition_variable>
#include <functional>
#include <iostream>
#include <future>
#include <vector>
#include <thread>
#include <queue>
 
class ThreadPool
{
public:
    using Task = std::function<void()>;
 
    explicit ThreadPool(std::size_t numThreads)
    {
        start(numThreads);
    }
 
    ~ThreadPool()
    {
        stop();
    }
 
    template<class T>
    auto enqueue(T task)->std::future<decltype(task())>
    {
        auto wrapper = std::make_shared<std::packaged_task<decltype(task()) ()>>(std::move(task));
 
        {
            std::unique_lock<std::mutex> lock{mEventMutex};
            mTasks.emplace([=] {
                (*wrapper)();
            });
        }
 
        mEventVar.notify_one();
        return wrapper->get_future();
    }
 
private:
    std::vector<std::thread> mThreads;
 
    std::condition_variable mEventVar;
 
    std::mutex mEventMutex;
    bool mStopping = false;
 
    std::queue<Task> mTasks;
 
    void start(std::size_t numThreads)
    {
        for (auto i = 0u; i < numThreads; ++i)
        {
            mThreads.emplace_back([=] {
                while (true)
                {
                    Task task;
 
                    {
                        std::unique_lock<std::mutex> lock{mEventMutex};
 
                        mEventVar.wait(lock, [=] { return mStopping || !mTasks.empty(); });
 
                        if (mStopping && mTasks.empty())
                            break;
 
                        task = std::move(mTasks.front());
                        mTasks.pop();
                    }
 
                    task();
                }
            });
        }
    }
 
    void stop() noexcept
    {
        {
            std::unique_lock<std::mutex> lock{mEventMutex};
            mStopping = true;
        }
 
        mEventVar.notify_all();
 
        for (auto &thread : mThreads)
            thread.join();
    }
};
 
int main()
{
    {
        ThreadPool pool{36};
 
        for (auto i = 0; i < 36; ++i)
        {
            pool.enqueue([] {
                auto f = 1000000000;
                while (f > 1)
                    f /= 1.00000001;
            });
        }
    }
 
    return 0;
}
  • Both of these lines (32 and 71) are in a lambda. – tkausl Jul 29 '21 at 06:04
  • @tkausl - Care to be bit more verbose? –  Jul 29 '21 at 06:05
  • The lambdas are placed in the queue, the thread pool calls the lambda. – tkausl Jul 29 '21 at 06:08
  • @tkausl - Still its not clear, why the code is attempting double execution. Your answers are not explaining clearly. Line at 31 looks like invoking the function (*wrapper)(); . isn't it? Line 72 is logically correct as its the right place for actually executing from queue. –  Jul 29 '21 at 06:24
  • Line 31 _is_ invoking the function. – tkausl Jul 29 '21 at 06:28
  • @tkausl - What is line 72 doing then? –  Jul 29 '21 at 06:29
  • Line 71 invokes the tasks popped from the queue. The lambda which contains line 31 will be on the queue. – tkausl Jul 29 '21 at 06:30
  • @tkausl - The tasks are wrapping the function to be executed and those are being added to the queue. So if line 72 is invoking the packaged tasks then it essentially means invoking the wrapped function which line 31 is already doing. Isnt it? The whole point of adding tasks to the queue is to be executed later. Why is line 31 executing the tasks when those are supposed to be executed later by thread at line 72? –  Jul 29 '21 at 06:32

1 Answers1

1

Line 32 adds a lambda which executes the task when invoked, while line 71 executes the lambda. One simply delegates to the other. In clear words, line 32 is just part of lambda which gets executed by invocation at line 72. We don't have double execution. Instead line 72 is the one which executes the code of which line 32 is part of.