スレッド プール 1 (パート 1 - 待機戦略)

       前書き:少し前に、私はメンターが欠陥検出という水平プロジェクトを行うのを手伝いました。展開するとき、推論を行うために OpenVINO を使用する予定でした。しかし、ワークピースの前後に 6 つの検出面があり、シングルスレッド推論があります。が遅すぎます。最速のワークは 9 秒以上です。次に、マルチスレッドについて考えました。マルチプロデューサー・マルチコンシューマーモデルに魔法のような変更を加えようとしていますが、変更すればするほどバグが増えます。それでも正直マルチスレッドをこすりました。このスレッドプールは今後もずっと使えると思って、もう少し完璧に書いてみます。さて、少し前のプロジェクトをまとめてみましょう。


        スレッド プールが解決する必要がある最も重要なことは、同時読み取りと書き込みの問題であり、これはマルチスレッドでも注意を払う必要がある問題です。スレッドがタスク キューと対話するときに、読み取りと書き込みが同時に行われます。待機戦略が有効になるのはこのときです。

        たとえば、タスク キューにタスクがない場合、ワーカー スレッドはタスクを取り出すまで待機する必要がありますか? タスクキューがタスクでいっぱいの場合でも、スレッドを挿入する場合は待つ必要がありますか? 待機が終了したら、1 つ以上のスレッドに通知する必要がありますか。これが待機戦略で行う必要があることです。

したがって、抽象親クラスが設計され、これらのアクションが仮想関数として記述され、サブクラスが親クラスを継承できるようになります。

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

次のステップは、どのような待機戦略を実装するかを検討することです。

一般的に使用されるのは、bolck (ブロック待機)、sleep (スレッド スリープ待機)、timeout (遅延待機) です。C++11 には、スレッド待機戦略としても使用できる yield() も用意されています。

1. ブロックはブロックされて待機中です。

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_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;
};

ここでは、空であると判断するロジックのみを実装します。この待機戦略を使用してスレッド プール全体を実行すると、外部コードがスレッドをウェイクアップし、ここではブロック ロジックのようにウェイクアップ コードをカプセル化する必要がないからです。

3. タイムアウト遅延待機

計時には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. 利回り戦略

c++11で追加されたスレッド管理手法で、現在のスレッドの実行権を放棄することで、他のスレッドに実行の機会を与え、スレッドの切り替えやスケジューリングを実現します。これは、強制的なプリエンプティブ スケジューリングではなく、スレッド認識に依存して切り替えを行う協調的なマルチタスク メカニズムです。

言い換えると、このコードが実行されると、スレッドは「一時停止」され (一時停止ではありませんが、一時停止に似ています)、現在のスレッドは独自のタイム スライスを終了し、その呼び出し優先順位をスレッド全体の最低レベルに置きます。プールにある場合は、他のスレッドを最初に実行させてから、それらのスレッドがウェイクアップされるか、独自のタイム スライスになるまで実行します。

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

ここでは空判定のロジックを実装しただけですが、理由は睡眠のロジックと同じです。すべては、自然なウェイクアップまたは外部のウェイクアップを待っている「一時停止」されたスレッドです。

参考:C++11ベースのスレッドプールの実装 - ほぼわかる

おすすめ

転載: blog.csdn.net/qq_35326529/article/details/130873107