2つのスレッドのブロックできないメッセージキュー

数年前、私の小さなゲームプロジェクトに取り組んでいたときに、あるスレッドから別のスレッドへのメッセージの転送を実装する必要がありました。 ソリューションの検索で、非ブロッキングキューを実装するというアイデアが生まれました。



カットの下の詳細。



問題の声明。

ライター(以降、ライター)とリーダー(リーダー)の2つのストリームがあります。 ロック(ミューテックスまたはその他のブロッキングメソッド)を使用せずに、ライターからリーダーにメッセージを送信するにはキューが必要です。



少し考えて、次の解決策を得ました。スレッドが互いに干渉しないように、2つの部分からなるキューを使用する必要があります。 1つの部分はライターによって使用され、他の部分はリーダーによって使用されます。 アルゴリズムは次のとおりです。

  1. ライターは、キューの自分の部分にメッセージを書き込み、リーダーのキューをチェックします。 空の場合、ライターの順番がリーダーに渡され、ライターの新しいキューが作成されます。
  2. リーダーはキューの自分の部分をチェックし、空でない場合はそこから値を取得します。


RSDNでの実装をレイアウトしました(リンクは記事の最後にあります)が、いくつかの議論の後、このトピックは収まりました。 そして、ここで私のベストプラクティスを共有したいと思います。



実際のコード:

#define LOCK_FREE_QUEUE2_USE_FLUSH 1 template <class TYPE> class LockFreeQueue2 { public: LockFreeQueue2(); ~LockFreeQueue2(); //     void Enqueue(TYPE* data); //     bool Dequeue(volatile TYPE*& data); #if LOCK_FREE_QUEUE2_USE_FLUSH //   .   bool Flush(); #else //      void SetWriterFinished(); #endif private: volatile TYPE *readerTop; //    TYPE *writerTop, *writerBottom; //      #if LOCK_FREE_QUEUE2_USE_FLUSH == 0 volatile bool isWriterFinished; #endif }; template<class TYPE> LockFreeQueue2<TYPE>::LockFreeQueue2() { readerTop = writerTop = writerBottom = NULL; #if LOCK_FREE_QUEUE2_USE_FLUSH == 0 isWriterFinished = false; #endif } template<class TYPE> LockFreeQueue2<TYPE>::~LockFreeQueue2() { } template<class TYPE> void LockFreeQueue2<TYPE>::Enqueue(TYPE* data) { data->next = NULL; if (writerTop) //     ,      { writerBottom->next = data; writerBottom = data; } else //    { writerTop = writerBottom = data; } if (!readerTop) //    ,     { readerTop = writerTop; writerTop = NULL; } } template<class TYPE> bool LockFreeQueue2<TYPE>::Dequeue(volatile TYPE*& data) { if (!readerTop) //    { #if LOCK_FREE_QUEUE2_USE_FLUSH == 0 if (isWriterFinished && writerTop) //   ,      { readerTop = writerTop; writerTop = NULL; } else #endif { return false; //   } } //     -  data = readerTop; readerTop = readerTop->next; return true; } #if LOCK_FREE_QUEUE2_USE_FLUSH template<class TYPE> bool LockFreeQueue2<TYPE>::Flush() { if (!writerTop) return true; if (!readerTop) //        { readerTop = writerTop; return true; } return false; } #else template<class TYPE> void LockFreeQueue2<TYPE>::SetWriterFinished() { isWriterFinished = true; } #endif
      
      







LOCK_FREE_QUEUE2_USE_FLUSH 定義は 、ライターの作業の最後に2つの異なるタイプのキュー動作をコンパイルするために使用されます。 値が1の場合、ライターはチャットキューをリーダーにスローするまでFlushメソッドを呼び出す必要があります。 値が0の場合、Writerは単純にisWriterFinished変数の値をtrueに設定して停止します。 読者自身が最後に残りを拾います。

また、ライターが永遠に待たないように、リーダーが作業を終了するにはフラグが必要なようです。



この実装には、いくつかの機能を追加できます。 たとえば、アイテムカウンターを追加できます。 また、カウンター変数への同時アクセスが発生しないように、キューの各部分ごとに分離する必要があります。 これらのカウンターの合計は、キュー内の要素の総数になります。

また、リーダーにイベントを追加することもできます。これに応じて、ライターが彼に自分の役割を与えたときに目覚めます。 しかし、すべてがそれほど単純ではなく、同時アクセスの問題が発生する可能性があります。

このキューは、1つのライターと多くのリーダーの状況には使用できませんが、ルーター(ディスパッチャー)と各リーダーに1つのキューを使用できます。 ルーターは、メッセージをキューに追加するリーダーを決定する必要があります。 最も単純な場合、この基準はキュー内の要素の最小数です。



記事を公開する前に、私はこのトピックについて少しグーグルで調べましたが、似たようなものは見つかりませんでした。 たぶん見た目が悪かったので、自転車を発明した場合や、実装に誤りを見つけたらコメントに書いてください。



RSDNのディスカッションへのリンク



All Articles