C++ thread pool (1) theoretical basis and simple implementation

Programmers who have written CURD must be familiar with the "database connection pool". Database connection is an expensive thing because its creation process is more resource-intensive. Therefore, in order to save resources and improve the performance of database operations, "database connection pool" came into being.

In fact, the thread pool is similar to the database connection pool. The creation and destruction of a thread also consumes resources. If threads are created and deleted repeatedly, the waste of resources is also considerable. Therefore, creating some threads in advance and assigning them to the corresponding threads to execute when there are tasks has a very positive effect on improving the performance of the system.

1. The creation process of the thread pool

1. Create a thread

for (size_t i = 0; i < threadCount; ++i) {
    threads.emplace_back(threadFunc, this);
}

threads is a vector, we can create some threads first, and put these threads into a vector.

2. Task queue and scheduling

void ThreadPool::addTask(const Task& task) {
    {
        lock_guard<mutex> lock(queueMutex);
        taskQueue.emplace(task);
    }
    condition.notify_one();
}

Newly incoming tasks will be added to the taskQueue queue, and you can take out tasks from the taskQueue according to a certain strategy and hand them over to an idle thread for execution.

3. Thread execution and recycling

void ThreadPool::threadFunc() {
    while (true) {
        Task task;
        {
            unique_lock<mutex> lock(queueMutex);
            //wait()用来等一个东西
            //1. 如果第2个参数为false,就跟只有1个参数一样,直接阻塞到本行
            //阻塞到什么时候为止呢?有其他持有锁的线程notify()后.
            //2. 当wait()被notify()唤醒后,会先判断第2个参数是否为true,如果为true才会
            //继续往下执行.
            condition.wait(lock, [this]() { return !taskQueue.empty() || terminate; });

            if (terminate && taskQueue.empty()) {
                break;
            }

            task = taskQueue.front();
            taskQueue.pop();
        }
        task(); // Execute the task.
    }
}

4. Graceful termination of the thread pool

In a multi-threaded program, how to terminate a thread is a problem that requires "careful" consideration. You cannot stop it directly, because this will cause a running thread to be abruptly interrupted, which will cause internal data inconsistencies!

We need to add a flag first: terminate. When the terminate flag is true, first stop the while task of the thread pool, then wake up all waiting threads, and use the std::thread::join() function to wait for the thread to finish executing .

ThreadPool::~ThreadPool() {
    terminate = true;
    condition.notify_all(); // 唤醒所有等待中的线程

    for (thread& th : threads) {
        if (th.joinable()) {
            th.join(); // 等待线程执行完毕
        }
    }
}

2. An example of a thread pool


#include <vector>
#include <queue>
#include <thread>
#include <iostream>
#include <stdexcept>
#include <condition_variable>
#include <memory>

#define MAX_THREADS 8 //最大线程数目

class Task{
public:
    void process(){
	
	}
};

class ThreadPool {
public:
    ThreadPool() = default;
	
    ThreadPool(const ThreadPool &) = delete;
    ThreadPool(ThreadPool &&) = delete;
    ThreadPool &operator = (const ThreadPool &) = delete;
    ThreadPool &operator = (ThreadPool &&) = delete;

	//初始化所有线程并启动线程
	void init(int threadCount){
		if (threadCount <= 0 || threadCount > MAX_THREADS) {
			throw std::exception();
		}

		for (int i = 0; i < threadCount; i++) {
			// emplace_back不能先构造再插入. 
			// 方法2: std::thread temp(worker, this); work_threads.push(temp);
			work_threads.emplace_back(worker, this);
		}
	}

	bool dispatch(std::shared_ptr<Task>&& request){
		// 操作工作队列时一定要加锁, 因为他被所有线程共享
		std::unique_lock<std::mutex> lock(queue_mutex);
		lock.lock();
		tasks_queue.push(request);
		lock.unlock();

		// 线程池添加进去了任务, 通知等待的线程
		condition.notify_one();

		return true;
	}

	~ThreadPool(){
		std::unique_lock<std::mutex> lock(queue_mutex);
		stop = true;
		 
		condition.notify_all();
		for (auto& w : work_threads) {
			w.join();
		}
	}

private:
	// 工作线程需要运行的函数,不断的从任务队列中取出并执行
	static void* worker(void* arg){
		ThreadPool* pool = (ThreadPool*)arg;
		pool->run();

		return pool;
	}

	void run(){
		while (!stop)
		{
			// unique_lock() 出作用域会自动解锁
			std::unique_lock<std::mutex> lk(this->queue_mutex);

			// 如果任务队列不为空,就停下来等待唤醒
			this->condition.wait(lk, [this] {
				return !this->tasks_queue.empty(); 
			});

			std::shared_ptr<Task> request = tasks_queue.front();
			tasks_queue.pop();
			if (request) {
				request->process();
			}
		}
	}

private:
	// 工作线程
	std::vector<std::thread> work_threads;
	
	// 任务队列
	std::queue<std::shared_ptr<Task>> tasks_queue;

	std::mutex queue_mutex;
	std::condition_variable condition;
	
	bool stop = false;
};

int main(){
    ThreadPool pool;
	pool.init(4);
	
    return 0;
}

reference:

(1) Implementation of thread pool based on C++11

(2) Easily master the C++ thread pool: from underlying principles to advanced applications

Guess you like

Origin blog.csdn.net/mars21/article/details/131461582