池式结构---线程池

原理部分:

线程池是什么?

        简单的说,就是一个管理一定数量线程的池式结构。

线程池解决的问题?

        解决了异步执行耗时任务(不过度占用核心线程),充分利用多核的特点。

耗时:耗时等待,耗时计算

线程池如何解决问题的?

以生产消费模型                                        生产者(线程)

搞清楚原理后,我们就开始手搓线程池了

首先,在纯C语言中,我们常常用struct结构体,和callback回调函数进行配合,在c++中引入了函数对象functional和lambda表达式来写线程池 

1.基本框架(ThreadPool)

class ThreadPool {
public:
    // 初始化线程池
    explicit ThreadPool(int threads_num);

    // 停止线程池
    ~ThreadPool();

    // 发布任务到线程池
    void Post(std::function<void()> task);

private:
    //工作线程
    void Worker();
    //存储工作线程的容器
    std::vector<std::thread> workers_;


    //堵塞队列
    std::unique_ptr<BlockingQueue<std::function<void()>>> task_queue_;

};

因为使用者只需要向线程池中Push任务,所以我们就只写Post函数对外就行了,然后还有我们的工作线程和存储工作线程的数组。

这时候我们就要考虑用什么容器来存储任务 

 我们应该能想到用队列(queue)来存储任务

很不错,但是队列不是线程安全的,所以这个时候我们需要自己进行实现一个线程安全的队列 ,我们叫它"堵塞队列"

2.任务队列的实现

template <typename T>
class BlockingQueue {
public:
    BlockingQueue(bool nonblock = false) : nonblock_(nonblock) { }
    // 入队操作
    void Push(const T &value) {
        std::lock_guard<std::mutex> lock(mutex_);
        queue_.push(value);
        not_empty_.notify_one();
    }
    // 正常 pop  弹出元素
    // 异常 pop  没有弹出元素
    bool Pop(T &value) {
        std::unique_lock<std::mutex> lock(mutex_);

        // 1. mutex_.unlock()
        // 2. queue_empty && !nonblock 线程在 wait 中阻塞
        // notify_one notify_all 唤醒线程
        // 3. 假设满足条件 mutex_.lock()
        // 4. 不满足条件 回到 2
        not_empty_.wait(lock, [this]{ return !queue_.empty() || nonblock_; });
        if (queue_.empty()) return false;

        value = queue_.front();
        queue_.pop();
        return true;
    }

    // 解除阻塞在当前队列的线程
    void Cancel() {
        std::lock_guard<std::mutex> lock(mutex_);
        nonblock_ = true;
        not_empty_.notify_all();
    }

private:
    bool nonblock_;   //true代表不堵塞
    std::queue<T> queue_;    
    std::mutex mutex_;
    std::condition_variable not_empty_;
};

在Push和Pop时,加上相应的锁,以防临界资源同时被多个线程修改,再根据条件变量进行线程的堵塞。

第一代线程池实现了

#include <thread>
#include <functional>
#include <vector>
#include <queue>
#include <mutex>
#include <condition_variable>

template <typename T>
class BlockingQueue {

public:
	BlockingQueue(bool nonblock = false) : nonblock_(nonblock){}

	void push(const T& value) {
		std::lock_guard<std::mutex> lock(mutex_);
		queue_.push(value);
		not_empty_.notify_one();
	}

	bool pop(T& value) {
		std::unique_lock<std::mutex> lock(mutex_);

		not_empty_.wait(lock, [this] {return !queue_.empty() || nonblock_;});
		if (queue_.empty()) return false;

		value = queue_.front();
		queue_.pop();
		return true;
	}

	void Cancel(){
		std::lock_guard<std::mutex> lock(mutex_);
		nonblock_ = true;
		not_empty_.notify_all();
	}
private:
	bool nonblock_;
	std::queue<T> queue_;
	std::mutex mutex_;
	std::condition_variable not_empty_;
};


class ThreadPool {

public:
	explicit ThreadPool(int threads_num) {
		task_queue_ = std::make_unique<BlockingQueuePro<std::function<void()>>>();
		for (size_t i = 0; i < threads_num; ++i) {
			workers_.emplace_back([this] {Worker(); });
		}
	}

	~ThreadPool() {
		task_queue_->Cancel();
		for (auto& worker : workers_) {
			if (worker.joinable())
				worker.join();
		}
	}

	void Post(std::function<void()> task) {
		task_queue_->Push(task);
	}

private:
	void Worker() {
		while (true) {
			std::function<void()> task;
			if (!task_queue_->Pop(task)) {
				break;
			}
			task();
		}
	}
	std::vector<std::thread> workers_;
	std::unique_ptr<BlockingQueue<std::function<void()>>> task_queue_;
};

优化:

我们根据一代代码,会发现有时消费者和生产者会同时抢一个锁,锁的碰撞粒度太大了,这时候我们想到维护两个队列,生产者队列,和消费者队列 

 

生产者线程只负责生产者队列,消费者线程只负责消费队列。

然后我们又会想到队列之中的任务,我们如何进行传递?

使用swap,当生产者队列中有任务时,消费者队列没任务时,这时将生产者队列里的任务移动到消费者队列中,此时,也是唯一一次两者会同时抢一把锁,锁的碰撞粒度大大的减小了。

template <typename T>
class BlockingQueuePro {
public:
    BlockingQueuePro(bool nonblock = false) : nonblock_(nonblock) {}

    void Push(const T &value) {
        std::lock_guard<std::mutex> lock(prod_mutex_);
        prod_queue_.push(value);
        not_empty_.notify_one();
    }

    bool Pop(T &value) {
        std::unique_lock<std::mutex> lock(cons_mutex_);
        if (cons_queue_.empty() && SwapQueue_() == 0) {
            return false;
        }
        value = cons_queue_.front();
        cons_queue_.pop();
        return true;
    }

    void Cancel() {
        std::lock_guard<std::mutex> lock(prod_mutex_);
        nonblock_ = true;
        not_empty_.notify_all();
    }

private:
    int SwapQueue_() {
        std::unique_lock<std::mutex> lock(prod_mutex_);
        not_empty_.wait(lock, [this] {return !prod_queue_.empty() || nonblock_; });
        std::swap(prod_queue_, cons_queue_);
        return cons_queue_.size();
    }

    bool nonblock_;
    std::queue<T> prod_queue_;
    std::queue<T> cons_queue_;
    std::mutex prod_mutex_;
    std::mutex cons_mutex_;
    std::condition_variable not_empty_;
};

测试代码:

#include <iostream>
#include <thread>
#include <vector>
#include <atomic>
#include <chrono>

// 包含 BlockingQueue 和 ThreadPool 的定义
#include "threadpool.h"

// 全局计数器,用于统计任务完成的数量
std::atomic<int> task_counter{0};

// 任务函数
void Task(int id) {
    std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 模拟任务执行时间
    std::cout << "Task " << id << " executed by thread " << std::this_thread::get_id() << std::endl;
    task_counter++; // 任务完成,计数器加一
}

// 生产者线程函数
void Producer(ThreadPool& pool, int producer_id, int num_tasks) {
    for (int i = 0; i < num_tasks; ++i) {
        int task_id = producer_id * 1000 + i; // 生成唯一的任务ID
        pool.Post([task_id]() {
            Task(task_id);
        }); // 提交任务到线程池
        std::cout << "Producer " << producer_id << " posted task " << task_id << std::endl;
    }
}

int main() {
    const int num_producers = 4;  // 生产者线程数量
    const int num_tasks_per_producer = 10; // 每个生产者提交的任务数量
    const int num_threads_in_pool = 2; // 线程池中的工作线程数量

    ThreadPool pool(num_threads_in_pool); // 创建线程池

    std::vector<std::thread> producers; // 生产者线程集合

    // 启动生产者线程
    for (int i = 0; i < num_producers; ++i) {
        producers.emplace_back(Producer, std::ref(pool), i, num_tasks_per_producer);
    }

    // 等待所有生产者线程完成
    for (auto& producer : producers) {
        producer.join();
    }

    // 等待所有任务完成
    while (task_counter < num_producers * num_tasks_per_producer) {
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }

    std::cout << "All tasks completed. Total tasks executed: " << task_counter << std::endl;

    return 0;
}

Tip:要注意头文件引用过深,引用循环 

从而引出了前置声明的概念

 

 

学习分享:https://github.com/0voice 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值