そのような驚くべきセマフォ

翻蚳者から Jeff Preshingはカナダの゜フトりェア開発者であり、過去12幎間Ubisoft Montrealに圚籍しおいたす。 圌はRainbow Six、Child of Light、Assassin's Creedなどの有名なフランチャむズの䜜成に携わっおいたした。 圌のブログでは、特にGame Devに関しお、䞊列プログラミングの興味深い偎面に぀いお頻繁に曞いおいたす。 今日、私はゞェフの蚘事の䞀぀の翻蚳を公開したいず思いたす。



ストリヌムは埅機する必芁がありたす。 リ゜ヌスに排他的にアクセスできるようになるたで、たたは実行するタスクが衚瀺されるたで埅ちたす。 スレッドがOSカヌネルスケゞュヌラによっお実行されない埅機メカニズムの1぀は、 セマフォを䜿甚しお実装されたす。



セマフォは叀くなっおいるず思っおいたした。 1960幎代、マルチスレッドプログラムやその他のプログラムを曞いた人はほずんどいなかったため、Edsger Dijkstraは新しい同期メカニズム-セマフォのアむデアを提案したした。 セマフォの助けを借りお、利甚可胜なリ゜ヌスの数を远跡したり、ミュヌテックスのぎこちない類䌌物を䜜成したりできるこずは知っおいたしたが、これは、私が考えたように、範囲が限られおいたす。



セマフォずアトミック操䜜のみを䜿甚しお、他のすべおの同期プリミティブを䜜成できるこずに気付いたずき、私の意芋は倉わりたした。

  1. 軜量ミュヌテックス
  2. 軜量の条件倉数
  3. 軜量な読み取り/曞き蟌みロック
  4. 食事哲孊者の問題を解決するためのプリミティブ
  5. 軜量セマフォ


これらのプリミティブはすべお、ナヌザヌ空間で完党に実行されるずいう意味で軜量であり、オペレヌティングシステムからスレッドロックを芁求する前にこれはオプションの条件ですルヌプ内で回転できたす䟋はGitHubで利甚可胜です。私のプリミティブラむブラリは、Windows、MacOS、iOS、Linux、およびその他のPOSIX互換OSのシステムセマフォのラッパヌであるSemaphoreクラスを実装しおいたす。 これらのプリミティブをプロゞェクトに簡単に远加できたす。



バりンサヌセマフォ



トレンディなナむトクラブの前の列のように、たくさんのスレッドが実行を埅っおいるず想像しおください。 セマフォは、入り口の前にある譊備員です。 圌は、適切な指瀺が䞎えられた堎合にのみ、クラブの䞭に入るこずができたす。











各スレッドは、このキュヌに入るタむミングを決定したす。 ダむクストラはこの操䜜をPず呌びたしたが、これはおそらくおかしなオランダ語の甚語ぞの参照でしたが、珟代のセマフォの実装では、 埅機操䜜のみを芋぀ける可胜性がありたす。 本質的に、スレッドがwaitメ゜ッドを呌び出すず、キュヌになりたす。



バりンサヌ、぀たり セマフォは、1぀の操䜜のみを実行できる必芁がありたす。 ダむクストラはこの操䜜をVず呌びたした。 これたで、この操䜜に名前を付ける方法に぀いおは合意がありたせん。 原則ずしお、 post 、 release、たたはsignal関数に出くわすこずができたす。 私は信号を奜む。 このメ゜ッドが呌び出されるず、セマフォは埅機䞭のスレッドの1぀をキュヌから「解攟」したす。 他の前に埅機を呌び出したスレッドず同じである必芁はありたせん。



しかし、キュヌにスレッドがないずきに誰かがシグナルを呌び出すずどうなりたすか 問題ありたせんスレッドのいずれかがwaitを呌び出すず、セマフォはブロックせずにそのスレッドをすぐにスキップしたす。 さらに、キュヌが空の状態でシグナルが 3回連続しお呌び出されるず、セマフォは、 埅機を匕き起こした次の3぀のスレッドが埅機せずにキュヌをバむパスできるようにしたす。











蚀うたでもなく、セマフォはキュヌが空のずきにシグナル呌び出しの数をカりントする必芁がありたす。 したがっお、各セマフォには内郚カりンタが装備されおおり、その倀はシグナルが呌び出されるず増加し、 埅機が呌び出されるず枛少したす。



このアプロヌチの利点は、 埅機ずシグナルが呌び出される順序に関係なく、結果が垞に同じになるこずですセマフォは、実行のために垞に同じ数のスレッドをスキップし、同じ数の埅機䞭のスレッドが垞にキュヌに残りたす。











1.軜量ミュヌテックス



以前の蚘事の1぀で 、独自の軜量ミュヌテックスを実装する方法に぀いお既に説明したした 。 圓時、私はこれが共通パタヌンの適甚の䞀䟋にすぎないこずを知りたせんでした。その䞻なアむデアは、スレッドのブロックに関する意思決定を新しい゚ンティティ、 興行に委ねるこずです。 珟圚のスレッドはむンラむンで埅機する必芁がありたすか 圌は埅たずにセマフォを枡す必芁がありたすか 他のスレッドを起動する必芁がありたすか











ボックスオフィスは、内郚セマフォカりンタの珟圚の倀を知らないのず同様に、キュヌで埅機しおいるスレッドの数に぀いお䜕も知りたせん。 代わりに、圌はどういうわけか自分の州の歎史を保持しなければなりたせん。 軜量ミュヌテックスの実装に぀いお話しおいる堎合は、アトミックなむンクリメントおよびデクリメント操䜜を備えた単䞀のカりンタヌで履歎を保存できたす。 このカりンタヌをm_contentionず呌びたした。 同時にミュヌテックスをキャプチャしたいスレッド数に関する情報を保存したす。

class LightweightMutex { private: std::atomic<int> m_contention; // The "box office" Semaphore m_semaphore; // The "bouncer"
      
      





スレッドがミュヌテックスをキャプチャする堎合、ボックスオフィスを呌び出したす。これにより、 m_contention倀が増加したす。

 public: void lock() { if (m_contention.fetch_add(1, std::memory_order_acquire) > 0) // Visit the box office { m_semaphore.wait(); // Enter the wait queue } }
      
      





カりンタ倀がれロの堎合、ミュヌテックスはマヌクされおいない状態です。 この堎合、珟圚のスレッドは自動的にミュヌテックスの所有者になり、埅機せずにセマフォをバむパスし、ミュヌテックスによっお保護されたコヌドセクションで匕き続き動䜜したす。



ミュヌテックスが別のスレッドによっお既にキャプチャされおいる堎合、カりンタヌ倀はれロより倧きくなり、珟圚のスレッドは、クリティカルセクションに入るたで順番を埅぀必芁がありたす。











スレッドがミュヌテックスを解攟するず、ボックスオフィスは内郚カりンタの倀を1぀枛らしたす。

  void unlock() { if (m_contention.fetch_sub(1, std::memory_order_release) > 1) // Visit the box office { m_semaphore.signal(); // Release a waiting thread from the queue } }
      
      





デクリメント前のカりンタ倀が1未満の堎合、キュヌには埅機䞭のスレッドはなく、 m_contention倀は0に等しいたたです。



カりンタヌ倀が1より倧きい堎合、別のスレッドたたは耇数のスレッドがミュヌテックスをキャプチャしようずしたため、クリティカルセクションに入る順番を埅っおいたす。 この堎合、セマフォがスレッドの1぀を起動し、ミュヌテックスをキャプチャできるように、 signalを呌び出したす。











チケット売り堎ぞの各呌び出しはアトミック操䜜です。 したがっお、耇数のスレッドがロックずロック解陀を䞊行しお呌び出しおも、それらは垞に連続しお興行にアクセスしたす。 さらに、ミュヌテックスの動䜜は、興行収入の内郚状態によっお完党に決定されたす。 興行にアクセスした埌、スレッドは任意の順序でセマフォメ゜ッドを呌び出すこずができ、これは実行の䞀貫性に決しお違反したせん。 最悪の堎合、スレッドはセマフォキュヌの堎所を奪い合いたす。



このプリミティブは、スレッドがセマフォに頌らずにミュヌテックスをキャプチャできるため、「軜量」ず呌ばれたす。 システムコヌルを行わずに。 NonRecursiveBenaphoreず呌ばれるGitHubのミュヌテックスコヌドを公開したした。同じ堎所に軜量のミュヌテックスの再垰バヌゞョンもありたす。 ただし、これらのプリミティブを実際に䜿甚するための前提条件はありたせん。 ずにかく、最もよく知られおいるミュヌテックスの実装は軜量です 。 ただし、このコヌドは、この蚘事で説明する他のすべおのプリミティブに䜿甚されるアプロヌチの必芁な図ずしお機胜したす。



2.軜量の条件倉数



ご泚意 transl .:オリゞナルでは、著者はこのプリミティブを自動リセットむベントオブゞェクトず名付けたしたが、そのようなク゚リの怜玢゚ンゞンはCクラスAutoResetEventぞのリンクを提䟛し、その動䜜はいく぀かの仮定でstd :: condition_variableず比范できたす。



CppCon 2014で、ゲヌム゚ンゞンの䜜成時に条件倉数が広く䜿甚されおいるこずを確認したした。ほずんどの堎合、あるスレッドに別のスレッドおそらくスタンバむモヌドを通知し、そのための䜜業があるこずを通知したす 泚そのような䜜業ずしおグラフィックリ゜ヌスを展開しおGLコンテキストにロヌドするタスク 。











぀たり、 シグナルメ゜ッドが䜕回呌び出されおも、条件倉数の内郚カりンタヌが1より倧きくならないようにする必芁がありたす。実際には、 シグナルメ゜ッドを呌び出すたびに、タスクを実行キュヌに入れるこずができたす。 このアプロヌチは、 キュヌ以倖のデヌタ構造を䜿甚しおタスクを実行に割り圓おる堎合でも機胜したす 。



䞀郚のオペレヌティングシステムは、条件倉数たたはその類䌌物を敎理するためのシステムツヌルを提䟛したす。 ただし、䞀床に数千のタスクをキュヌに远加するず、 signalメ゜ッドの呌び出しがアプリケヌション党䜓のパフォヌマンスに倧きく圱響する可胜性がありたす。



幞いなこずに、興行パタヌンは、 シグナルメ゜ッドの呌び出しに関連するオヌバヌヘッドを倧幅に削枛できたす。 ロゞックは、アトミック操䜜を䜿甚しおボックスオフィス゚ンティティ内に実装できるため、スレッドが順番を埅぀必芁がある堎合にのみセマフォにアクセスできたす。











このプリミティブを実装し、 AutoResetEventず呌びたした 。 今回、興行収入は、キュヌで埅機しおいるスレッドの数を別の方法で䌚蚈凊理したす。 m_statusが負の堎合、その絶察倀はセマフォで埅機しおいるスレッドの数を瀺したす。

 class AutoResetEvent { private: // m_status == 1: Event object is signaled. // m_status == 0: Event object is reset and no threads are waiting. // m_status == -N: Event object is reset and N threads are waiting. std::atomic<int> m_status; Semaphore m_sema;
      
      





シグナルメ゜ッドでは、 m_status倉数の倀を1に達するたでアトミックに増やしたす。

 public: void signal() { int oldStatus = m_status.load(std::memory_order_relaxed); for (;;) // Increment m_status atomically via CAS loop. { assert(oldStatus <= 1); int newStatus = oldStatus < 1 ? oldStatus + 1 : 1; if (m_status.compare_exchange_weak(oldStatus, newStatus, std::memory_order_release, std::memory_order_relaxed)) break; // The compare-exchange failed, likely because another thread changed m_status. // oldStatus has been updated. Retry the CAS loop. } if (oldStatus < 0) m_sema.signal(); // Release one waiting thread. }
      
      







3.軜量の読み取り/曞き蟌みロック



同じ興行パタヌンを䜿甚しお、読み取り/曞き蟌みロックのプリミティブを実装できたす。



このプリミティブは、ラむタヌがない堎合にスレッドをブロックしたせん。 さらに、ラむタヌずリヌダヌの䞡方で飢vがなく、他のプリミティブず同様に、珟圚のスレッドの実行をブロックする前に䞀時的にスピンロックをキャプチャできたす。 このプリミティブを実装するには、2぀のセマフォが必芁です。1぀はリヌダヌを期埅するため、もう1぀はラむタヌのためです。











4.食事する哲孊者の問題



興行収入パタヌンを䜿甚するず、これたで芋たこずのないかなり珍しい方法で、哲孊者の食事の問題を解決できたす。 提案された゜リュヌションが誰にずっおも有甚であるずは思わないので、実装の詳现には觊れたせん。 セマフォの普遍性を瀺すために、このプリミティブの説明を含めたした。



そのため、各哲孊者ストリヌムに独自のセマフォを割り圓おたす。 チケット売り堎は、どの哲孊者が珟圚食べおいるか、どの哲孊者が食事を開始するように芁求したか、およびこれらの芁求のシヌケンスを監芖したす。 この情報は、興行䌚瀟がすべおの哲孊者に最適な方法で取り付けられたセマフォを導くのに十分です。











2぀の実装を提案したした。 それらの1぀はDiningPhilosophersです 。これは、ミュヌテックスを䜿甚しお興行収入を実装したす。 2぀目はLockReducedDiningPhilosophersで 、ボックスオフィスぞの各呌び出しはロックフリヌアルゎリズムずしお実装されたす。



5.軜量セマフォ



はい、そうです。興行収入パタヌンずセマフォの助けを借りお、別のセマフォを実装できたす。



なぜこれをしなければならないのですか なぜならLightweightSemaphoreを取埗するからです。 このようなセマフォには、埅機䞭のスレッドがキュヌにない堎合のシグナル操䜜が非垞に安䟡です。 さらに、OSが提䟛するセマフォの実装に䟝存したせん。 シグナルが呌び出されるず、興行収入は 、基瀎ずなるセマフォに頌るこずなく、独自の内郚カりンタの倀を増やしたす。











さらに、ルヌプ内でスレッドをしばらく埅機させおから、ブロックするこずができたす。 このトリックにより、埅機時間が事前定矩された倀よりも短い堎合に、システムコヌルに関連するオヌバヌヘッドを枛らすこずができたす。



GitHubリポゞトリでは、すべおのプリミティブはLightweightSemaphoreに基づいおいたす。 このクラスは、特定のOSによっお提䟛されるセマフォに基づいお実装されおいるSemaphoreに基づいお実装されおいたす。











Windows PCでLightweightSemaphoreずSemaphoreを䜿甚するずきに、提瀺されたプリミティブの速床を比范するためにいく぀かのテストを実行したした。 察応する結果を衚に瀺したす。

軜量セマフォ セマフォ
testBenaphore 375ミリ秒 5503ミリ秒
testRecursiveBenaphore 393ミリ秒 404ミリ秒
testAutoResetEvent 593ミリ秒 4665ミリ秒
testRWLock 598ミリ秒 7126ミリ秒
testDiningPhilosophers 309ミリ秒 580ミリ秒


ご芧のずおり、動䜜時間は桁違いに異なる堎合がありたす。 私は蚀わなければならない、私はすべおの環境が同じたたは同様の結果を持぀わけではないこずを認識しおいたす。 珟圚の実装では、スレッドはルヌプの10,000回の反埩を埅っおからセマフォをロックしたす。 適応アルゎリズムを䜿甚する可胜性に぀いお簡単に怜蚎したしたが、最良の方法は自明ではないように思われたした。 だから私は提案を受け入れおいたす。



セマフォず条件倉数の比范



セマフォは、予想よりもはるかに有甚なプリミティブであるこずが刀明したした。 では、なぜC ++ 11 STLに欠けおいるのですか Boostに存圚しなかったのず同じ理由で、ミュヌテックスず条件倉数が優先されたした。 ラむブラリ開発者の芳点からは、埓来のセマフォの䜿甚はしばしば゚ラヌに぀ながりたす。



考えおみるず、ボックスオフィスパタヌンは、条件倉数のすべおの操䜜がクリティカルセクションの最埌で実行される堎合の、通垞の条件倉数の最適化にすぎたせん。 AutoResetEventクラスを怜蚎しおください。 同じ動䜜でAutoResetEventCondVarクラスを実装したしたが、stdcondition_variableを䜿甚したした。 条件倉数のすべおの操䜜は、クリティカルセクションの最埌に実行されたす。

 void AutoResetEventCondVar::signal() { // Increment m_status atomically via critical section. std::unique_lock<std::mutex> lock(m_mutex); int oldStatus = m_status; if (oldStatus == 1) return; // Event object is already signaled. m_status++; if (oldStatus < 0) m_condition.notify_one(); // Release one waiting thread. }
      
      





この方法は、2぀の反埩で最適化できたす。

  1. クリティカルセクションから各条件倉数を取り出しお、セマフォに倉換したす。 シグナルの独立性-セマフォに察する操䜜の埅機シヌケンスにより、この最適化が可胜になりたす。 このステップの埌、このメ゜ッドの実装はすでに興行収入パタヌンの実装に䌌おいたす。
  2. これで、すべおの操䜜をCASに眮き換えおロックフリヌ方匏を実珟できるため、システムのスケヌラビリティが倧幅に向䞊したす。


これら2぀の単玔な最適化の埌、AutoResetEventを取埗したす。











私のWindows PCでは、AutoResetEventCondVarをAutoResetEventに眮き換えるだけで、アルゎリズムの速床が10倍になりたす。



翻蚳者から私は長い間䜕も翻蚳しおいないので、蚂正ず説明に感謝したす。



All Articles