2010-11-21 3 views
5

単一プロデューサスレッドのシングルコンシューマスレッドのロックレスキューがあり、プロデューサがデータを生成することなく長期間使用する可能性があるとします。待ち行列に何もないときにコンシューマースレッドをスリープさせることは有益です(節電と他のプロセス/スレッドのためのCPUの解放)。キューがロックレスでない場合、この問題を解決する簡単な方法は、生成スレッドがミューテックスをロックし、その作業を行い、条件変数を通知してロックを解除し、読み取りスレッドがミューテックスをロックするために、それを読んでから、ロックを解除してください。しかし、ロックレスキューを使用している場合は、ミューテックスを使用すると全く同じ方法で、まずロックレスキューを使用することで得られるパフォーマンスがなくなります。ロックレスキューをポーリングするための最速レースフリーメソッドは何ですか?

純粋な解決策は、キューに挿入するたびにプロデューサがミューテックスをロックし、条件変数に信号を送り、ミューテックスをロック解除し、実際の作業(キューへの挿入)を完全にロックの外に保ち、消費者に同じことをさせて、mutexをロックし、条件変数を待って、ロックを解除し、すべてをキューから引き出し、繰り返して、ロックの外にあるキューの読みを保持します。ここには競合状態があります。読者が待ち行列から抜け出して寝る間に、プロデューサーがアイテムを待ち行列に挿入した可能性があります。今度は読者が眠りにつき、プロデューサーが別の商品を挿入して再び条件変数に信号を送るまで無期限に滞在することがあります。これは、キューを通過するのに非常に時間がかかるような特定のアイテムで時々終わることがあることを意味します。あなたのキューが常に常時アクティブである場合、これは問題ではないかもしれませんが、常にアクティブだった場合は、条件変数を完全に忘れる可能性があります。

AFAICT解決策は、プロデューサが通常のニーズロックキューを使用している場合と同じように動作することです。ミューテックスをロックし、ロックレスキューに挿入し、条件変数を通知し、ロックを解除する必要があります。しかし、消費者は異なる行動をとるべきです。起動すると、キューを読み取るまで待つのではなく、すぐにmutexのロックを解除する必要があります。次に、できるだけ多くのキューをプルして処理する必要があります。最後に、消費者がスリープ状態に入ることを考えているときにのみ、ミューテックスをロックし、データがあるかどうかを確認し、アンロックして処理するか、そうでなければ条件変数を待ちます。このようにして、ミューテックスはlockfullキューより少ない頻度で競合しますが、まだキューに残っているデータでスリープするリスクはありません。

これを行う最も良い方法ですか?代わりがありますか?

:「最速」とは、私は本当に「何度もキューをチェックするコアを捧げることなく、最速」を意味するが、それがタイトルに収まらない; P

1つの代替:素朴な解決方法を試してみましょうが、コンシューマは、キューを通過するアイテムに対して許容する最大レイテンシに対応するタイムアウトを条件変数に設定します。ただし、希望のタイムアウトがかなり短い場合は、ご使​​用のOSの最小待ち時間を下回っている場合や、CPUが多すぎる場合があります。

+0

プロデューサーが何かを生産するたびに条件変数に信号を送ることはできませんか?なぜミューテックスが必要なのでしょうか? – Gabe

+0

@ギャベ:2つの理由。第1に、この場合、プロデューサは何かを生成し、消費者がアイテムの処理を終えたときと条件変数を待つことを決定したときとの間で信号を発する可能性があるからです。その後、消費者は眠りにつき、プロデューサーが作ったアイテムは次回信号が発せられるまで待ち行列に留まります。第2に、少なくともpthreads APIでは、mutexなしで条件変数を使用することができないためです。あなたは実際にmutexをwait関数に渡す必要があります。条件変数のすべての実装が実際にそれらを必要としているかどうかはわかりません。 –

+0

@Gabe:条件変数を待っているものが何もないときに信号が発せられたとき、次回に何かが条件変数を待つときに即座に目覚めてしまうが、その事件は起こらない。信号が発せられたときに条件変数を待っていなければ、それは決して起こったことはありません。この意味で、条件変数を待つことは、ファイル/ソケット/パイプでpoll/selectを使うこととは異なります。 –

答えて

1

Dr Dobbs articleなどのロックフリーのシングルプロデューサシングルコンシューマキューを使用していると仮定していますので、その用語を使用します。

その場合には、「AFAICT」を起動する段落のご提案の答えは良いですが、私はそれがわずかに最適化することができると思う。消費者に

  • を - あなたが言うように、消費者が使い尽くされたときキューと寝検討(だけにして)されたが、それは、ミューテックスをロックし、再びキューをチェックして、
    • のいずれかがミューテックスを解放し、作業に運び、新しい項目がキュー
    • であった場合、または条件変数のブロック(空でないキューを見つけるために起きたときにミューテックスを解放する、nat尿道)。プロデューサーで
    • まずlastのコピーを取り、
    • saved_lastそれを呼び出すsaved_dividerそれを呼び出す、そしてdividerポインタのコピーを取り、いつものようにアイテムnew_itemを追加します。
    • saved_dividerの値がnew_itemの場合は、挿入したオブジェクトがオブジェクトになり、オブジェクトがすでに消費され、作業が完了しています。
    • saved_dividerの値がsaved_lastと等しくない場合は、消費者を起床する必要はありません。これは次のとおりです、あなたはdividerはまだどちらかnew_itemたり、挿入を開始して以来saved_last
    • に達していなかったことを知っているあなたの新しいオブジェクトを追加し、厳密後時
      • lastは、これら2つの値を持っていただけた、
      • dividerlast
      • と等しい場合、消費者は今までに停止します。したがって、就寝前に消費者は目を覚ましていなければなりません。
    • それ以外の場合は、mutexをロックし、condvarに信号を送り、mutexを解放します。 (ここでは、ミューテックスを取得することは、あなたが空であり、実際condvar上のブロッキングキューに気付いて、消費者の間の時間にcondarを知らせることがなくなります。)

これは、その場合には保証します消費者はビジー状態のままですが、消費者がまだ起きている(スリープ状態ではない)ことを知っているときは、ミューテックスをロックしないでください。また、ミューテックスが保持されている時間を最小限に抑えて、競合の可能性をさらに低減します。

上記の説明は非常にわかりやすいです(なぜアルゴリズムがあるのではなく、なぜ機能するのか説明したかったからです)が、そのコードは非常にシンプルでなければなりません。

もちろん、実際に実行する価値があるかどうかは、多くのことに依存します。パフォーマンスが重要であるかどうかを測定することをおすすめします。 mutex/condvarプリミティブの優れた実装は、ほとんどの場合、内部でアトミック操作を使用するため、一般的にカーネル呼び出しを行うだけです(最も高価なビットです)。ブロックする必要がある場合や、スレッドが待機しているため、mutex関数を呼び出さないことで節約される時間は、ライブラリ呼び出しのオーバーヘッドに過ぎません。

関連する問題