线程池 1(第一部分--等待策略)

        前言:之前一段时间,在帮导师做了一个横向的项目,缺陷检测,在部署的时候,准备用OpenVINO去做推理,但是一个工件前前后后6个检测面,单线程推理太慢了,一个工件最快也要9秒多。然后想着要不上多线程。就准备在多生产者-多消费者模型上魔改,然后越改bug越多。。。还是老老实实手搓了一个多线程。想着这个线程池可以以后一直用就准备写的稍微完善一点。现在总结一下前段时间的项目。


        线程池最主要要解决的是读写并发的问题,这也是多线程中必须要注意的问题。读写并发出现在线程和任务队列交互的时候。等待策略也就是在这个时候起效的。

        比如,当任务队列没有任务的时候,工作线程要取出任务是不是要等待。当任务队列满任务时,想要插入线程是不是还要要等待。        当等待结束,是不是得通知一个或者多个线程。这都是等待策略需要干的事情。

所以设计了一个抽象父类,把这些动作都写成虚函数,然后让子类去继承这个父类。

class WaitStrategy {
public:
	virtual void Notifyone() = 0;		// 通知一个线程
	virtual void BreakAllwait() = 0;	// 通知全部线程
	virtual bool EmptyWait() = 0;		// 判断当前任务队列是否为空,如果为空则等待
	virtual ~WaitStrategy(){}
};

接下来就要考虑实现什么等待策略了。

常用的有bolck(阻塞等待),sleep(线程睡眠等待),timeout(延时等待),c++11还提供了yiled()这种方式也可以作为线程等待策略。

1、block阻塞等待。

class BlockWaitStrategy : public WaitStrategy {
public:
	// 构造函数
	BlockWaitStrategy(){}

	// 只通知一个线程 
	void Notifyone() override{
		cv_.notify_one();
	}

	// 通知所有线程
	void  BreakAllwait() override {
		cv_.notify_all();
	}

	// 判断当前任务队列是否为空,如果为空则等待
	bool EmptyWait() override{
		std::unique_lock<std::mutex> lk(mutex_);
		cv_.wait(lk);
		return false;
	}

private:
	std::mutex mutex_;
	std::condition_variable cv_;
};

这对代码其实很好理解的,一个锁,一个条件变量。唯一复杂一点就是EmptyWait();实际上放在应用场景里还是很好理解的,当子线程需要取出任务时,发现队列为空,无法取出任务,那么就得等待队列中有任务了才能去取。在等待时,得判断任务队列是否为空,那么就用到这个函数。

2、Sleep睡眠等待

也就是利用线程中的sleep_for();来让线程进入休眠状态,当休眠时间结束,或者当前线程被条件变量唤醒,则继续执行。

class  SleepWaitStratrgy : public WaitStrategy
{
public:
	SleepWaitStratrgy() {}
	explicit SleepWaitStratrgy(std::uint64_t sleep_time_us) :sleep_time_us_(sleep_time_us) {}
	// 判断当前是否为空等待
	bool EmptyWait() override {
		std::this_thread::sleep_for(std::chrono::microseconds(sleep_time_us_));
		return true;
	}

	// 设置睡眠时间
	void SetSleepTimeMicroSeconds(uint64_t sleep_time_us) {
		sleep_time_us_ = sleep_time_us;
	}

private:
	std::uint64_t sleep_time_us_ = 10000;
};

这里我只实现了判空的逻辑,因为如果用这个等待策略去执行整个线程池的话,外部的代码会唤醒该线程,不需要这里像block逻辑一样去封装一下唤醒的代码。

3、Timeout 延时等待

利用chrono库来进行定时,定时时间到了,再进行处理。

class TimeoutWaitStrategy : public WaitStrategy
{
public:
	TimeoutWaitStrategy() {}
	explicit TimeoutWaitStrategy(std::uint64_t timeout) : time_out_(std::chrono::milliseconds(timeout)) {}

	// 通知一个线程
	void Notifyone() override{
		cv_.notify_one();
	}

	// 通知所有线程
	void BreakAllwait() override {
		cv_.notify_all();
	}

	// 判断队列是否为空 并进行等待操作
	bool EmptyWait() override {
		std::unique_lock<std::mutex> lk(mutex_);
		// 等待条件变量通知,若time_out_时间内没有被通知,则返回false,队列不为空。
		if (cv_.wait_for(lk, time_out_) == std::cv_status::timeout)
			return false;
		// 时间到了,没有被通知则返回true,队列不为空
		return true;
	}

	// 设置阻塞时间
	void SetTimeout(uint64_t timeout) {
		time_out_ = std::chrono::milliseconds(timeout);
	}

private:
	std::mutex mutex_;
	std::condition_variable cv_;
	std::chrono::milliseconds time_out_;
};

4、yiled策略

c++11中添加的一种线程管理方式,通过让出当前线程的执行权,让其他线程有机会执行,从而实现了线程的切换和调度。它是一种协作式多任务处理的机制,依赖于线程的自觉性来进行切换,而不是强制性的抢占式调度。

说人话也就是,当运行到这条代码时,线程“挂起”(也不是挂起,但类似于挂起),当前线程退出自己的时间片,然后把自己的调用优先级,放在整个线程池的最低,让别的线程先执行,直到被唤醒或者轮到自己的时间片了,再执行。

class YieldWaitStrategy : public WaitStrategy
{
public:
	YieldWaitStrategy(){}
	bool EmptyWait() override {
		std::this_thread::yield();
			return true;
	}
};

这里我只实现了判空的逻辑,理由和sleep的逻辑是一样的。都是是线程“暂停”等待自然唤醒,或者外部唤醒。

参考:基于C++11实现线程池 - 知乎

猜你喜欢

转载自blog.csdn.net/qq_35326529/article/details/130873107
今日推荐