【后端开发】手写一个简单的线程池

半同步半异步线程池

半同步半异步线程池分为三层:

  • 同步服务层 ------ 处理来自上层的任务请求,将它们加入到排队层中等待处理。

  • 同步排队层 ------ 实际上是一个"同步队列",允许多线程添加/取出任务,并保证线程安全。

  • 异步服务层 ------ 从排队层中取出任务,多线程并发处理排队层中的任务。

不想码字,想看的凑活着看吧!

首先,我们来实现一个 同步队列 的模板:

cpp 复制代码
#pragma once

#include<iostream>
#include<thread>
#include<mutex>
#include<list>


template <typename T>
class Sync_Queue
{
public:
	Sync_Queue(int size) : max_size(size), _stop(false){}

	void push(T&& x)    // 添加任务
	{
		std::unique_lock<std::mutex> lock(_mutex);
		_notFull.wait(lock, [this] { return NotFull() || _stop; });  // 若满足其中任一条件,则继续执行

		if (_stop)
			return;
		_queue.push_back(std::forward<T>(x));
		_notEmpty.notify_one();
	}

	void pop(std::list<T>& list)    // 取出任务
	{
		std::unique_lock<std::mutex> lock(_mutex);
		_notEmpty.wait(lock, [this] { return NotEmpty() || _stop; });

		if (_stop)
			return;
		list = std::move(_queue);
		_notFull.notify_one();
	}

	void stop()    // 停止队列
	{
		{
			std::lock_guard<std::mutex> lock(_mutex);    // 先锁住, 再将 _stop 标志设置为 true
			_stop = true;
		}
		_notFull.notify_all();   // 在 lock_guard 外面 notify, 被唤醒的线程不需要等待 lock_guard 释放锁 
		_notEmpty.notify_all();
	}

	bool Empty()
	{
		std::lock_guard<std::mutex> lock(_mutex);
		return _queue.empty();
	}

	bool Full()
	{
		std::lock_guard<std::mutex> lock(_mutex);
		return _queue.size() == max_size;
	}

	size_t size()
	{
		std::lock_guard<std::mutex> lock(_mutex);
		return _queue.size();
	}

private:
	bool NotFull() const
	{
		bool notfull = _queue.size() < max_size;
		if (!notfull) std::cout << "Sync_Queue is full, waiting..." << std::endl;
		return notfull;
	}

	bool NotEmpty() const
	{
		bool notempty = !_queue.empty();
		if (!notempty) std::cout << "Sync_Queue is empty, waiting..." << std::endl;
		return notempty;
	}

private:
	std::list<T> _queue;
	std::mutex _mutex;
	std::condition_variable _notEmpty;    // 非空的条件变量
	std::condition_variable _notFull;     // 未满的条件变量
	int max_size;
	bool _stop;
};

现在,我们再来实现 线程池

cpp 复制代码
// ThreadPool.h
#pragma once

#include "Sync_Queue.h"
#include <atomic>
#include <memory>
#include <functional>

using Task = std::function<void()>;  // 任务类型为一个 "可调用对象"

const int MaxTaskCount = 100;

class ThreadPool {
public:
	ThreadPool(int thread_num = std::thread::hardware_concurrency())  // 默认创建 CPU 核数的线程
		: _queue(MaxTaskCount), thread_stop(false)
	{
		start(thread_num);
	}

	~ThreadPool()
	{
		stop();
	}

	void stop()
	{
		std::call_once(_flag, [this] { StopThreadPool(); });  // 确保多线程下只调用一次
	}

	void add_task(Task&& task)      // 添加任务
	{
		_queue.push(std::forward<Task>(task));
	}

private:
	void start(int thread_num)     // 创建 thread_num 数量的线程
	{
		for (int i = 0; i < thread_num; ++i)
		{
			thread_group.push_back(std::make_shared<std::thread>(&ThreadPool::RunInThread, this));
		}
	}

	void RunInThread()
	{
		while (!thread_stop) {
			std::list<Task> list;
			_queue.pop(list);      // 取任务; 若消息队列为空,则阻塞

			for (auto& task : list)
			{
				if (thread_stop)
					return;
				task();     // 执行任务
			}
		}
	}

	void StopThreadPool()
	{
		_queue.stop();
		thread_stop = true;

		for (auto thread : thread_group) {
			if (thread->joinable())
				thread->join();
		}

		thread_group.clear();
	}

private:
	Sync_Queue<Task> _queue;   // 同步队列
	std::list<std::shared_ptr<std::thread>> thread_group;   // 线程组
	std::atomic_bool thread_stop;
	std::once_flag _flag;
};

测试代码:

cpp 复制代码
#include "ThreadPool.h"
#include <chrono>

void test()
{
    ThreadPool pool(3);

    std::thread t1([&pool] {
        for (int i = 0; i < 10; ++i)
        {
            auto id = std::this_thread::get_id();
            pool.add_task(std::move([id] {
                std::cout << "thread id is " << id << std::endl;
            }));
        }
    });

    std::this_thread::sleep_for(std::chrono::seconds(2));
    getchar();
    t1.join();
}

int main()
{
    test();

    return 0;
}

输出如下:

相关推荐
程序员鱼皮1 小时前
我代表编程导航,向大家道歉!
前端·后端·程序员
zjjuejin1 小时前
Maven 生命周期与插件机制
后端·maven
阿杆2 小时前
为什么我建议你把自建 Redis 迁移到云上进行托管
redis·后端
Java水解2 小时前
go语言教程(全网最全,持续更新补全)
后端·go
bobz9652 小时前
QEMU 使用 DPDK 时候在 libvirt xml 中设置 sock 的目的
后端
thinktik2 小时前
AWS EKS 计算资源自动扩缩之按需申请Fargate[AWS 中国宁夏区]
后端·aws
thinktik2 小时前
AWS EKS 实现底层EC2计算资源的自动扩缩[AWS 中国宁夏区]
后端·aws
uhakadotcom3 小时前
什么是OpenTelemetry?
后端·面试·github
知其然亦知其所以然3 小时前
MySQL 社招必考题:如何优化特定类型的查询语句?
后端·mysql·面试
用户4099322502123 小时前
给接口加新字段又不搞崩老客户端?FastAPI的多版本API靠哪三招实现?
后端·ai编程·trae