Simple thread pool

Keywords: C++ thread thread pool

outline

Author in Simple thread pool In this paper, the author will use blocking thread synchronization to realize the thread pool with the same characteristics.

This article will not repeat and Simple thread pool The same content. If there is any ambiguity, please refer to This blog.

realization

The following code shows the implementation of this thread pool.

class Thread_Pool {

  private:

    struct Task_Wrapper { ...

    };

    atomic<bool> _done_;
    Blocking_Queue<Task_Wrapper> _queue_;            // #1
    unsigned _workersize_;
    thread* _workers_;

    void work() {
        Task_Wrapper task;
        while (!_done_.load(memory_order_acquire)) {
            _queue_.pop(task);                         // #3
            task();
        }
    }

    void stop() {
        while (!_queue_.empty())                // #2
            std::this_thread::yield();
        _done_.store(true, memory_order_release);
        for (unsigned i = 0; i < _workersize_; ++i)        // #5
            _queue_.push([] {});
        for (unsigned i = 0; i < _workersize_; ++i) {
            if (_workers_[i].joinable())
                _workers_[i].join();                    // #4
        }
        delete[] _workers_;
    }

  public:
    Thread_Pool() : _done_(false) {
        try {
            _workersize_ = thread::hardware_concurrency();
            _workers_ = new thread[_workersize_];
            for (unsigned i = 0; i < _workersize_; ++i) {
                _workers_[i] = thread(&Thread_Pool::work, this);
            }
        } catch (...) {
            stop();
            throw;
        }
    }
    ~Thread_Pool() {
        stop();
    }

    template<class Callable>
    future<typename std::result_of<Callable()>::type> submit(Callable c) {
        typedef typename std::result_of<Callable()>::type R;
        packaged_task<R()> task(c);
        future<R> r = task.get_future();
        _queue_.push(std::move(task));
        return r;
    }

};

Task_Wrapper materializes thread safe task queue blocking_ Queue<>(#1). Later on blocking_ Queue < > explain.

When the thread pool is terminated, the task queue will be emptied after the remaining tasks in the task queue are executed (#2). In_ done_ Before it is set to true, the worker thread may_ queue_.pop(task) and enter the blocking state of loop waiting (#3). If the main thread calls the join() function (#4) of the worker thread first at this time, it will cause a deadlock state. That is, the worker thread is waiting for a task to queue, and the main thread has to wait for the end of the worker thread. In order to break the loop waiting condition, the main thread puts in the queue before calling the worker thread's join () function_ workersize_ A fake task (#5). The purpose is to ensure that all worker threads waiting on the task queue exit the circular wait (#3).


Blocking thread safe task queue blocking_ The queue < > is as follows:,

template<class T>
class Blocking_Queue {

  private:
    mutex mutable _m_;            // #1
    condition_variable _cv_;
    queue<T> _q_;

  public:
    void push(T&& element) {
        lock_guard<mutex> lk(_m_);
        _q_.push(std::move(element));
        _cv_.notify_one();                // #3
    }

    void pop(T& element) {
        unique_lock<mutex> lk(_m_);
        _cv_.wait(lk, [this]{ return !_q_.empty(); });    // #2
        element = std::move(_q_.front());
        _q_.pop();
    }

    bool empty() const {
        lock_guard<mutex> lk(_m_);
        return _q_.empty();
    }

    size_t size() const {
        lock_guard<mutex> lk(_m_);
        return _q_.size();
    }

};

The queue adopts std::mutex and std::condition_variable controls the concurrent access of the worker thread to the task queue (#1). If there are no dequeued tasks, the current thread will_ cv_ Upper cycle waiting (#2); After the task joins the team, the_ cv_ Notifies the thread waiting on it (#3).

logic

The following class diagram shows the main logical structure of the code of this thread pool.

[note] in the figure, the initial function of the working thread is identified with a stereotype, and the calling relationship is explained in the annotation.

The following sequence diagram shows the concurrent process of thread pool user submitting tasks and worker thread executing tasks.

verification

Verify the test cases and results used, and Simple thread pool Consistent.

last

For a complete example, please refer to [github] a_simple_thread_pool.cpp .

The author refers to C + + concurrent programming practice / written by Williams (a.); Translated by Zhou Quan et al. - Beijing: People's Posts and Telecommunications Press, 2015.6 (reprinted in April 2016). To Anthony Williams, Zhou Quan and other translators.

Posted by freakstyle on Mon, 29 Nov 2021 23:13:08 -0800