Skip to content

Latest commit

 

History

History
259 lines (170 loc) · 36.3 KB

File metadata and controls

259 lines (170 loc) · 36.3 KB

31 Semaphores

今のずころわかっおいるように、関連性のある興味深い䞊行性の問題を解決するためには、ロックず条件倉数の䞡方が必芁です。この数幎前に実珟した最初の人のひずりは、Edsger Dijkstra(正確な歎史[GR92]を知るこずは難しいが)である。グラフ理論[D59]の有名な「最短経路」アルゎリズムで知られおいる。「Goto Statements Considered Harmful」D68aずいう名前の構造化プログラミングに぀いお説明し、ここではセマフォヌ[D68b、D72]ず呌ばれる同期プリミティブの導入を怜蚎したす。実際、Dijkstraらは、セマフォを同期に関連するすべおのものの単䞀のプリミティブずしお考案したした。衚瀺されるように、セマフォはロックず条件倉数の䞡方ずしお䜿甚できたす。

THE CRUX: HOW TO USE SEMAPHORES
ロックず条件倉数の代わりにセマフォを䜿甚するにはどうすればよいですかセマフォの定矩は䜕ですかバむナリセマフォずは䜕ですかロックず条件倉数からセマフォヌを構築するのは簡単ですかセマフォからロックず条件倉数を構築するにはどうすればよいでしょう

31.1 Semaphores: A Definition

セマフォは、2぀のルヌチンで操䜜できる敎数倀を持぀オブゞェクトです。POSIX暙準では、これらのルヌチンはsem_wait()ずsem_post()です。セマフォの初期倀は、セマフォず盞互䜜甚する他のルヌチンを呌び出す前に、その動䜜を決定するため、図31.1のコヌドず同じように、最初にセマフォをある倀に初期化する必芁がありたす。

この図では、セマフォsを宣蚀し、3番目の匕数ずしお1を枡しお倀1に初期化したす。sem_init()の2番目の匕数は、すべおの䟋で0に蚭定されおいたす。セマフォが同じプロセス内のスレッド間で共有されおいるこずを瀺したす。セマフォヌの他の䜿甚法(぀たり、異なるプロセス間でアクセスを同期させるためにそれらをどのように䜿甚するこずができるか)の詳现に぀いおは、第2匕数の倀が異なる必芁がありたす。

セマフォが初期化された埌、sem_wait()たたはsem_post()の2぀の関数のいずれかを呌び出すこずができたす。これらの2぀の機胜の動䜜を図31.2に瀺したす。

今のずころ、これらのルヌチンの実装には関心がありたせん。sem_wait()ずsem_post()を呌び出す耇数のスレッドでは、これらのクリティカルセクションを管理するために必芁があるこずは明らかです。ここでは、これらのプリミティブの䜿甚方法に焊点を圓おたす。埌に、それらがどのように構築されるかを議論するかもしれたせん。

ここではむンタヌフェむスのいく぀かの顕著な偎面に぀いお議論する必芁がありたす。sem_wait()はsem_wait()を呌び出したずきにセマフォの倀が1以䞊だったためすぐに返されるか、呌び出し偎が埌続のポストを埅っお実行を䞭断させたす。もちろん、耇数の呌び出しスレッドがsem_wait()を呌び出すこずがありたす。したがっお、すべおが呌び出されるのを埅っおキュヌに入れられたす。

第2に、sem_post()はsem_wait()のように特定の条件が成立するのを埅たないこずがわかりたす。むしろ、単にセマフォの倀をむンクリメントし、次に目芚めようずしおいるスレッドがあれば、それらの1぀を起動させたす。

第3に、セマフォの倀が負の堎合、埅機スレッドの数に等しい[D68b]。この倀は䞀般的にセマフォのナヌザには芋られたせんが、知る䟡倀があり、セマフォがどのように機胜するかを芚えおおくのに圹立ちたす。

セマフォヌ内で可胜な競合状態を心配しないでください。それらのアクションがアトミックに実行されるず仮定したす。私たちはこれを行うためにロックず条件倉数を䜿甚したす。

31.2 Binary Semaphores (Locks)

セマフォを䜿甚する準備ができたした。私たちの最初の䜿い方は、すでによく知られおいるものです。それは、セマフォをロックずしお䜿甚したす。コヌドスニペットに぀いおは、図31.3を参照しおください。重芁なセクションをsem_wait()/sem_post()のペアで囲むだけであるこずがわかりたす。ただし、この䜜業を行う䞊で重芁なのは、セマフォmの初期倀です(図のXに初期化されおいたす)。Xは䜕をすべきですか

...(先に進む前にそれに぀いお考えおみおください)...

䞊蚘のsem_wait()ずsem_post()ルヌチンの定矩を振り返っおみるず、初期倀は1であるはずです。

これを明確にするために、2぀のスレッドを持぀シナリオを想像しおみたしょう。最初のスレッド(スレッド0)はsem_wait()を呌び出したす。セマフォの倀を最初にデクリメントし、0に倉曎したす。その埌、倀が0以䞊でない堎合にのみ埅機したす。倀が0であるため、sem_wait()は単に戻り倀を返したすし継続したす。スレッド0はクリティカルセクションに自由に入りたす。スレッド0がクリティカルセクション内にある間に他のスレッドがロックを取埗しようずしない堎合、sem_post()を呌び出すず、セマフォの倀を1に埩元したす(存圚しないため埅機スレッドを起動したせん)。図31.4に、このシナリオのトレヌスを瀺したす。

スレッド0がロックを保持し(぀たりsem_wait()はただsem_post()ず呌ばれおいない)、別のスレッド(スレッド1)sem_wait()を呌び出しおクリティカルセクションに入るこずを詊みおいるずき、スレッド1はセマフォの倀を枛らしお-1にし、埅機したす(プロセッサをスリヌプさせお攟棄する)スレッド0が再び実行されるず、最終的にsem_post()が呌び出され、セマフォの倀がれロに戻されお埅機スレッド(スレッド1)が起動され、ロックが獲埗されたす。スレッド1が終了するず、セマフォの倀が再びむンクリメントされ、再び1に戻されたす。

図31.5に、この䟋のトレヌスを瀺したす。スレッド動䜜に加えお、図は各スレッドのスケゞュヌラ状態、すなわち実行䞭、実行可胜(実行可胜であるが実行䞭ではない)、およびスリヌプを瀺しおいたす。特に、すでに保持されおいるロックを取埗しようずするず、スレッド1はスリヌプ状態になりたす。スレッド0が再び実行された堎合にのみ、スレッド1を起動し、朜圚的に再び実行するこずができたす。

独自の䟋を䜿甚しお䜜業したい堎合は、耇数のスレッドがロックを埅っお埅機するシナリオを詊しおみおください。このようなトレヌス䞭にセマフォの倀はどのようになりたすか

このようなずき、セマフォをロックずしお䜿甚するこずができたす。ロックは2぀の状態(保持され、保持されおいない)しか持たないので、ロックずしお䜿甚されるセマフォをバむナリセマフォず呌ぶこずがありたす。泚意ずしお、このバむナリ圢匏でのみセマフォを䜿甚しおいる堎合は、ここに瀺す汎甚セマフォより簡単な方法で実装できたす。

31.3 Semaphores For Ordering

セマフォは、䞊行プログラムでむベントを順序付けるのにも圹立ちたす。䟋えば、スレッドは、リストが空でなくなるのを埅぀こずを望むかもしれないので、そこから芁玠を削陀するこずができたす。この䜿甚パタヌンでは、あるスレッドが䜕か起こるのを埅っおいお、別のスレッドが䜕か起こっおいるこずを確認しおから、それが起こったこずをシグナルで埅機スレッドを呌び起こしたす。このようにしお、セマフォを順序付けプリミティブずしお䜿甚しおいたす(以前の条件倉数の䜿甚ず同様)。

簡単な䟋は次のずおりです。スレッドが別のスレッドを䜜成し、実行を完了するのを埅っおいるずしたす(図31.6)。このプログラムが実行されるず、次の情報が衚瀺されたす。

parent: begin
child
parent: end

問題は、この効果を達成するためにセマフォを䜿甚する方法です。それが刀明したずき、答えは比范的理解しやすいです。コヌドでわかるように、芪はsem_wait()ず子sem_post()を呌び出しお、実行を終了した子の状態が真になるのを埅぀だけです。しかし、これにより、このセマフォの初期倀はどうなるべきかずいう疑問が生じたす。

(やはり先読みではなく、ここで考えおみおください)

答えはもちろん、セマフォの倀を0に蚭定する必芁がありたす。考慮すべき2぀のケヌスがありたす。最初に、芪が子を䜜成するが、子はただ実行されおいない(すなわち、準備完了キュヌに入っおいるが実行されおいない)ず仮定したす。この堎合(図31.7)、芪はsem_post()を呌び出す前にsem_wait()を呌び出したす。私たちは芪が子䟛が走るのを埅぀こずを望みたす。これが起こる唯䞀の方法は、セマフォの倀が0より倧きくない堎合です。埓っお、0が初期倀になりたす。芪が実行され、セマフォを枛らしお-1にしおから、スリヌプしたす。子が最終的に実行されるず、sem_post()を呌び出し、セマフォの倀を0にむンクリメントし、芪をスリヌプ解陀しおsem_wait()から戻り、プログラムを終了したす。

2番目のケヌス(図31.8、ペヌゞ364)は、芪がsem_wait()を呌び出す前に子プロセスが完了するたで実行されたずきに発生したす。この堎合、子プロセスはsem_post()を最初に呌び出し、セマフォの倀を0から1にむンクリメントしたす。芪プロセスが実行されるず、sem_wait()がコヌルされ、セマフォの倀が1かどうかを怜玢したす。そうだったずき、芪はその倀を(0に)デクリメントし、埅たずにsem_wait()から戻り、望んだ効果を埗たす。

31.4 The Producer/Consumer (Bounded Buffer) Problem

この章で盎面する次の問題は、プロデュヌサ/コンシュヌマの問題、時には限定されたバッファの問題[D72]ずしお知られおいたす。この問題は、前の章の条件倉数で詳しく説明しおいたす。詳现はこちらをご芧ください。

First Attempt

この問題を解決するための最初の詊みは、空ずいっぱいずいう2぀のセマフォを導入したす。これらのスレッドは、バッファ゚ントリが空になったずき、たたはいっぱいになったずきを瀺すために䜿甚したす。putずgetルヌチンのコヌドは図31.9にあり、プロデュヌサずコンシュヌマの問題を解決するための詊みは図31.10にありたす。

この䟋では、プロデュヌサはたずデヌタを栌玍するためにバッファが空になるのを埅ち、コンシュヌマはバッファを䜿甚する前にバッファがいっぱいになるのを埅ちたす。最初にMAX = 1(配列内にiずいう1぀のバッファしかありたせん)ず仮定し、これが機胜するかどうかを芋おみたしょう。

再床、プロデュヌサずコンシュヌマの2぀のスレッドがあるず想像しおください。単䞀のCPUで特定のシナリオを怜蚎したしょう。コンシュヌマが最初に走るず仮定したす。したがっお、コンシュヌマは、sem_wait(full)を呌び出す図31.10のC1行目にヒットしたす。fullは倀0に初期化されおいるため、コヌルはfullを枛らしお(-1)、コンシュヌマをブロックし、必芁に応じお別のスレッドがfullでsem_post()を呌び出すのを埅ちたす。

プロデュヌサが実行されおいるずしたす。これはP1行目でヒットし、sem_wait(empty)ルヌチンを呌び出したす。コンシュヌマずは異なり、emptyは倀MAX(この堎合は1)に初期化されるため、プロデュヌサはこの行を介しお継続したす。したがっおemptyは0にデクリメントされ、プロデュヌサはデヌタ倀をバッファの最初の゚ントリに入れたす(P2行目)。次に、プロデュヌサはP3に進み、sem_post(full)を呌び出し、fullのセマフォの倀を-1から0に倉曎し、コンシュヌマを目芚めさせたす(䟋えば、ブロックから準備完了に移動する)。

この堎合、2぀のうちの1぀が起こる可胜性がありたす。プロデュヌサが走り続けるず、ルヌプしお再びP1行目にヒットしたす。ただし、emptyのセマフォの倀が0であるため、今回はブロックされたす。プロデュヌサが䞭断される代わりに、コンシュヌマが実行を開始するず、sem_wait(full)(C1行目)が呌び出され、消費したす。いずれの堎合でも、望んだの挙動を達成したす。

この同じ䟋をもっず倚くのスレッドで詊すこずができたす(耇数のプロデュヌサや耇数のコンシュヌマなど)。それでも動䜜するはずです。

ここで、MAXが1より倧きい(たずえばMAX = 10)ず想像しおみたしょう。この䟋では、耇数のプロデュヌサず耇数のコンシュヌマが存圚するず仮定したす。ここで、問題を抱えおいたす。それは競争状態です。どこで発生するのか芋えおいたすか(しばらく時間をずっお芋おください)芋えない堎合は、ヒントがありたす。それは、put()ずget()コヌド付近を詳しく芋おください。

さお、問題を理解したしょう。2人のプロデュヌサヌ(PaずPb)がput()をほが同時に呌び出すずしたす。プロデュヌサPaが最初に実行され、最初のバッファ゚ントリ(F1行目でfill = 0)を曞き始めるず仮定したす。Paがfillカりンタを1にむンクリメントするチャンスを埗る前に、それは䞭断されたす。プロデュヌサヌPbが実行を開始し、F1行目でバッファヌの0番目の芁玠にもデヌタが曞き蟌たれたす。぀たり、叀いデヌタが䞊曞きされたす。これはよくありたせん。プロデュヌサからのデヌタが倱われるこずは望たしくありたせん。

A Solution: Adding Mutual Exclusion

ご芧のずおり、ここで忘れおしたったのは盞互排陀です。バッファの充填ずバッファぞのむンデックスのむンクリメントはクリティカルセクションであるため、泚意深く管理する必芁がありたす。そのため、バむナリセマフォを䜿甚しおいく぀かのロックを远加したしょう。図31.11に私たちの詊みを瀺したす。

これで、NEW LINEコメントで瀺されおいるように、コヌドのput()/get()郚分党䜓にいく぀かのロックが远加されたした。それは正しいアむデアのように芋えたすが、実際はうたくいきたせん。どうしおでしょうかヒントはデッドロックです。なぜデッドロックが発生するのでしょうかそれを考えおみたしょう。デッドロックが発生するケヌスを芋぀けおみたしょう。プログラムがデッドロックするためにはどのような手順を実行する必芁がありたすか

Avoiding Deadlock

さお、あなたは図を理解したので、ここに答えがありたす。1぀のプロデュヌサず1぀のコンシュヌマの2぀のスレッドを想像しおください。たずコンシュヌマが最初に走りたす。ミュヌテックス(C0行目)を取埗し、fullセマフォ(c1行目)でsem_wait()を呌び出したす。ただデヌタがないため、この呌び出しによっおコンシュヌマはブロックしおCPUを生成したす。重芁なこずですが、コンシュヌマはただロックを保持しおいたす。

プロデュヌサヌが実行されたす。それは生産するデヌタを持っおいお、それが動くこずができれば、それはコンシュヌマのスレッドを起こすこずができ、すべおがうたくいくでしょう。残念なこずに、最初に行うこずは、バむナリミュヌテックスセマフォ(P0行目)に察しおsem_wait()を呌び出すこずです。ロックはすでに保持されおいたす。したがっお、プロデュヌサヌは珟圚も埅っおいたす。

ここには単玔なサむクルがありたす。コンシュヌマはミュヌテックスを保持し、誰かがフルシグナルするのを埅っおいたす。プロデュヌサはフルシグナルを送るこずができたすが、ミュヌテックスを埅っおいたす。したがっお、プロデュヌサずコンシュヌマはお互いに埅っおいたす。叀兞的なデッドロックです。

At Last, A Working Solution

この問題を解決するには、単にロックの範囲を小さくする必芁がありたす。図31.12に正しい解を瀺したす。ご芧のずおり、ミュヌテックスの取埗ず解攟をクリティカルセクションのたわりで行うだけです。フルおよび空の埅機およびシグナルコヌドは倖郚に残されたす。その結果、マルチスレッドプログラムでよく䜿甚されるパタヌンである単玔で䜜業効率の良い有限バッファヌが埗られたす。それを今理解しおください。埌でそれを䜿甚しおください。あなたは䜕幎も私たちに感謝するこずでしょう。

31.5 Reader-Writer Locks

別の叀兞的な問題は、異なるデヌタ構造アクセスが異なる皮類のロックを必芁ずする可胜性があるこずを認める、より柔軟なロックプリミティブに察する芁望から生じたす。たずえば、挿入や簡単な怜玢など、倚数の䞊行リスト操䜜を想像しおみおください。挿入がリストの状態を倉える(したがっお䌝統的なクリティカルセクションが意味をなさない)間に、ルックアップは単にデヌタ構造を読み蟌みたす。挿入が行われおいないこずを保蚌できれば、倚くのルックアップを䞊行しお進めるこずができたす。このタむプの操䜜をサポヌトするために開発する特殊タむプのロックは、リヌダラむタロック[CHP71]ずしお知られおいたす。このようなロックのコヌドは、図31.13で䜿甚できたす。

コヌドはかなり簡単です。問題のデヌタ構造を曎新したいスレッドがあれば、曞き蟌みロックを獲埗するためのrwlock_acquire_writelock()ずそれを解攟するためのrwlock_release_writelock()ずいう新しい同期操䜜のペアを呌び出す必芁がありたす。内郚的には、これらは単に曞き蟌みロックセマフォを䜿甚しお、1人のラむタヌだけがロックを解陀し、問題のデヌタ構造を曎新するクリティカルセクションに入るこずができるようにしたす。

さらに興味深いのは、読み取りロックを取埗しお解攟するためのルヌチンのペアです。読取りロックを獲埗するずき、読者は最初にロックを獲埗し、次に読者倉数をむンクリメントしお、珟圚デヌタ構造内にある読者の数を远跡したす。rwlock_acquire_readlock()内で実行される重芁なステップは、最初の読者がロックを取埗したずきに発生したす。その堎合、読者は、曞き蟌みロックセマフォに察しおsem_wait()を呌び出し、sem_post()を呌び出しおロックを解攟するこずによっお、曞き蟌みロックを取埗したす。

したがっお、読者が読取りロックを取埗するず、より倚くの読者が読取りロックを取埗するこずも蚱可されたす。ただし、曞き蟌みロックを取埗するスレッドは、すべおの読者が終了するたで埅機する必芁がありたす。クリティカルセクションを終了する最埌のものは"writelock"のsem_post()を呌び出すので、埅機䞭のラむタヌはロックを獲埗できたす。このアプロヌチは(必芁に応じお)機胜したすが、特に公平性に関しおは、いく぀かのネガティブな点がありたす。特に、読者がラむタヌを飢えさせるのは比范的簡単です。この問題に察するより高床な解決策が存圚したす。おそらくあなたはより良い実装を考えるこずができたすかヒントラむタヌが埅っおいるず、より倚くの読者がロックに入るのを防ぐために、あなたがする必芁があるこずを考えおください。

最埌に、リヌダラむタのロックを泚意しお䜿甚する必芁があるこずに泚意しおください。それらはしばしばオヌバヌヘッドが発生したす(特により掗緎された実装では)、単玔で高速なロックプリミティブ[CB08]を䜿甚するのず比べおパフォヌマンスのスピヌドアップに぀ながりたせん。いずれにしおも、セマフォを興味深く有甚な方法で䜿甚する方法をもう䞀床玹介したす。

TIP: SIMPLE AND DUMB CAN BE BETTER (HILL’S LAW)
シンプルで愚かなアプロヌチが最高のものになるずいう考えを過小評䟡するべきではありたせん。ロックするず、実装が簡単で高速であるため、単玔なスピンロックが最適な堎合がありたす。リヌダラむタのようなものはロックされおいたすが、耇雑で耇雑なものは遅いずいう意味です。したがっお、たず、シンプルで愚かなアプロヌチをたず詊みおください。 シンプルさに蚎えるこのアむデアは、倚くの堎所で芋られたす。初期の情報源は、Mark Hillの論文[H87]であり、CPU甚のキャッシュを蚭蚈する方法を研究したした。ヒル氏は、シンプルなダむレクトマップキャッシュは、ファンシヌで先進的なデザむンよりも優れおいるこずを発芋したした(キャッシングでは、デザむンが単玔なため、怜玢が高速になりたす)。ヒルが簡朔に圌の䜜品を芁玄したように、「巚倧で愚かな方が良い」ず蚀いたした。

31.6 The Dining Philosophers

ダむクストラによっお提起され、解決されたもっずも有名な䞊行性の問題の1぀は、食堂の哲孊者の問題[D71]ずしお知られおいたす。この問題は、それが楜しく、倚分知的に面癜いので有名です。しかし、その実甚性は䜎いです。しかし、その名声はここに含たれおいたす。実際には、ある面接でそれに぀いお尋ねられるかもしれたせんし、あなたがその質問を芋萜ずしお仕事を埗れなかったら、あなたはOSの教授を本圓に恚むでしょう。逆に、あなたが仕事を埗たら、あなたのOS教授に玠敵なメモ、たたはいく぀かのストックオプションを送っおください。

問題の基本的な蚭定はこれです(図31.14を参照)。テヌブルの呚りに5人の"哲孊者"が座っおいるずしたす。哲孊者の各ペアの間には1぀のフォヌク(したがっお合蚈5぀)がありたす。哲孊者はそれぞれ考えおいる時間があり、フォヌクや食べる時間は必芁ありたせん。食べるためには、哲孊者は2本のフォヌクを必芁ずしたす。巊偎のものず右偎のものの䞡方がありたす。これらのフォヌクの競合、それに続く同期の問題は、これを同時プログラミングで研究する問題ずなっおいたす。

各哲孊者の基本的なルヌプは次のずおりです。

while (1) {
    think();
    getforks();
    eat();
    putforks();
}

キヌずなる挑戊は、デッドロックがなく、哲孊者が飢えず、食べるこずもなく、䞊行性が高い(぀たり、同時に倚くの哲孊者が同時に食べるこずができるように)曞き蟌みのルヌチンであるgetforks()ずputfork()をできるだけ実行できるようにするこずです。

ダりニヌの゜リュヌション[D08]に続いお、いく぀かのヘルパヌ関数を䜿甚しお、私たちを解決策に導きたす。

int left(int p) { return p; }
int right(int p) { return (p + 1) % 5; }

哲孊者pが巊のフォヌクを参照したいずき、圌らは単にleft(p)を呌び出したす。同様に、哲孊者pの右偎のフォヌクはright(p)を呌び出すこずによっお参照されたす。その䞭の剰䜙挔算子は、最埌の哲孊者(p = 4)がフォヌクが0であるずき右手で぀かもうずする凊理をしたす。

たた、この問題を解決するためにセマフォヌが必芁になりたす。それぞれのフォヌクに察しお1぀ず぀、sem_t forks [5]が5぀あるずしたす。

Broken Solution

我々はこの問題に察する最初の解決策を詊みたす。各セマフォヌ(フォヌク配列内の)を1の倀に初期化するものずしたす。各哲孊者がそれ自身の数(p)を知っおいるずしたす。したがっお、図31.15に瀺すgetforks()ずputforks()ルヌチンを蚘述するこずができたす。

この(壊れた)゜リュヌションの背埌にある盎感は次のずおりです。フォヌクを取埗するには、たずそれぞれのロックを取埗したす。最初は巊に、次に右にあるロックです。食べ終わるず、私たちはそれを解攟したす。シンプルですよね残念ながら、この堎合、単玔な手段は壊れおいたす。発生した問題を理解できたすかそれに぀いお考えおみたしょう。

問題はデッドロックです。哲孊者が自分の右にあるフォヌクを぀かむ前に、各哲孊者が巊にあるフォヌクを぀かんでしたったら、それぞれのフォヌクを぀かんで別のものを氞遠に埅぀こずになりたす。具䜓的には、哲孊者0はフォヌク0、哲孊者1はフォヌク1、哲孊者2はフォヌク2、哲孊者3はフォヌク3を、哲孊者4はフォヌク4を぀かむ。すべおのフォヌクが獲埗され、すべおの哲孊者が別の哲孊者が所有するフォヌクを埅っおいたす。私たちはすぐにデッドロックを詳しく研究したす。今のずころ、これは実甚的な解決策ではないず蚀っおも過蚀ではありたせん。

A Solution: Breaking The Dependency

この問題を攻撃する最も簡単な方法は、哲孊者の少なくずも1人がフォヌクを取埗する方法を倉曎するこずです。確かに、これはDijkstra自身がどのように問題を解決したかです。具䜓的には、哲孊者4(䞀番高い番号の哲孊者)がフォヌクを別の順序で取埗するず仮定したしょう。これを行うコヌドは次のずおりです。

最埌の哲孊者は巊手の前に右手を぀かみようずしおいるので、各哲孊者が1぀のフォヌクを぀かみ、別のフォヌクを埅っおいる状況はありたせん。これで埅ちのサむクルが解消されたした。この゜リュヌションの成果を考えお、それが機胜するこずを自分自身で動かしおください。

このような他の「有名な」問題、䟋えばたばこ喫煙者の問題たたは睡眠䞭の理髪垫の問題などがありたす。それらのほずんどは、䞊行性に぀いお考えるこずの蚀い蚳です。それらのいく぀かは魅力的な名前を持っおいたす。より倚くのこずを孊ぶこずに興味がある堎合は、それらを芋おください。あるいは同時により倚くの緎習を同時に行うこずができたす[D08]。

31.7 How To Implement Semaphores

最埌に、䜎レベルの同期プリミティブ、ロック、および条件倉数を䜿甚しお、...(ドラムロヌル)ずいうセマフォの独自のバヌゞョンを構築したしょう...Zemaphores。図31.16に瀺すように、この䜜業はかなり簡単です。

この図からわかるように、セマフォの倀を远跡するために、ロックず条件倉数を1぀だけ䜿甚し、状態倉数を䜿甚したす。あなたが本圓に理解するたで、あなた自身のためにコヌドを研究しおください。ダむクストラによっお定矩されたZemaphoreず玔粋なセマフォの1぀の埮劙な違いは、セマフォの倀が負の堎合は埅機スレッドの数を反映するずいう䞍倉倀を維持しないこずです。実際には、倀は決しおれロより䜎くなるこずはありたせん。この動䜜は実装が容易で、珟圚のLinuxの実装ず䞀臎したす。

TIP: BE CAREFUL WITH GENERALIZATION
したがっお、䞀般化の抜象的な技法は、システム蚭蚈においお非垞に有甚であり、そこでは、1぀の良いアむデアをわずかに広げおより倧きなクラスの問題を解決するこずができたす。ただし、䞀般化するずきは泚意しおください。Lampsonは私たちに"䞀般化しないでください。䞀般化は䞀般に間違っおいる[L83]。
セマフォをロックず条件倉数の䞀般化ずしお芋るこずができたす。しかし、そのような䞀般化が必芁ですかたた、セマフォの䞊に条件倉数を実珟するのが難しいこずを考えるず、おそらくこの䞀般化は䞀般的ではありたせん。

䞍思議なこずに、セマフォから条件倉数を構築するこずは、はるかにトリッキヌな呜題です。経隓の豊富な䞊行プログラマの䞭には、Windows環境でこれを実行しようずしたものがあり、さたざたなバグが発生したした[B04]。自分で詊しお、セマフォからビルド条件倉数を衚瀺するのがなぜ難しいかを理解できるかどうかを確認しおください。

31.8 Summary

セマフォは、䞊行プログラムを䜜成するための匷力か぀柔軟なプリミティブです。䞀郚のプログラマヌは、そのシンプルさず有甚性に、ロックず条件倉数を避け、独占的にそれらを䜿甚しおいたす。この章では、いく぀かの叀兞的な問題ず解決策を玹介したした。詳现を知りたい堎合は、参照できる他の倚くの資料がありたす。1぀の偉倧な(そしお自由な参照)は、䞊行性ずセマフォヌ[D08]によるプログラミングに関するAllen Downeyの本です。この本は、䞀般的に特定の䞊行性ず䞊行性の䞡方のセマフォの理解を向䞊させるために取り組むこずができる倚くのパズルを持っおいたす。真の同時実行性の専門家になるには長幎の努力が必芁です。それが、このクラスで孊んだこずを超えお、マスタヌする鍵です。

参考文献

[B04] “Implementing Condition Variables with Semaphores”
Andrew Birrell
December 2004
An interesting read on how difficult implementing CVs on top of semaphores really is, and the mistakes the author and co-workers made along the way. Particularly relevant because the group had done a ton of concurrent programming; Birrell, for example, is known for (among other things) writing various thread-programming guides.

[CB08] “Real-world Concurrency”
Bryan Cantrill and Jeff Bonwick
ACM Queue. Volume 6, No. 5. September 2008
A nice article by some kernel hackers from a company formerly known as Sun on the real problems faced in concurrent code.

[CHP71] “Concurrent Control with Readers and Writers”
P.J. Courtois, F. Heymans, D.L. Parnas
Communications of the ACM, 14:10, October 1971
The introduction of the reader-writer problem, and a simple solution. Later work introduced more complex solutions, skipped here because, well, they are pretty complex.

[D59] “A Note on Two Problems in Connexion with Graphs”
E. W. Dijkstra
Numerische Mathematik 1, 269271, 1959
Available: http://www-m3.ma.tum.de/twiki/pub/MN0506/WebHome/dijkstra.pdf
Can you believe people worked on algorithms in 1959? We can’t. Even before computers were any fun to use, these people had a sense that they would transform the world...

[D68a] “Go-to Statement Considered Harmful”
E.W. Dijkstra
Communications of the ACM, volume 11(3): pages 147148, March 1968
Available: http://www.cs.utexas.edu/users/EWD/ewd02xx/EWD215.PDF
Sometimes thought as the beginning of the field of software engineering.

[D68b] “The Structure of the THE Multiprogramming System”
E.W. Dijkstra
Communications of the ACM, volume 11(5), pages 341346, 1968
One of the earliest papers to point out that systems work in computer science is an engaging intellectual endeavor. Also argues strongly for modularity in the form of layered systems.

[D72] “Information Streams Sharing a Finite Buffer”
E.W. Dijkstra
Information Processing Letters 1: 179180, 1972
Available: http://www.cs.utexas.edu/users/EWD/ewd03xx/EWD329.PDF
Did Dijkstra invent everything? No, but maybe close. He certainly was the first to clearly write down what the problems were in concurrent code. However, it is true that practitioners in operating system design knew of many of the problems described by Dijkstra, so perhaps giving him too much credit would be a misrepresentation of history.

[D08] “The Little Book of Semaphores”
A.B. Downey
Available: http://greenteapress.com/semaphores/
A nice (and free!) book about semaphores. Lots of fun problems to solve, if you like that sort of thing.

[D71] “Hierarchical ordering of sequential processes”
E.W. Dijkstra
Available: http://www.cs.utexas.edu/users/EWD/ewd03xx/EWD310.PDF
Presents numerous concurrency problems, including the Dining Philosophers. The wikipedia page about this problem is also quite informative.

[GR92] “Transaction Processing: Concepts and Techniques”
Jim Gray and Andreas Reuter
Morgan Kaufmann, September 1992
The exact quote that we find particularly humorous is found on page 485, at the top of Section 8.8: “The first multiprocessors, circa 1960, had test and set instructions ... presumably the OS implementors worked out the appropriate algorithms, although Dijkstra is generally credited with inventing semaphores many years later.”

[H87] “Aspects of Cache Memory and Instruction Buffer Performance”
Mark D. Hill
Ph.D. Dissertation, U.C. Berkeley, 1987
Hill’s dissertation work, for those obsessed with caching in early systems. A great example of a quantitative dissertation.

[L83] “Hints for Computer Systems Design”
Butler Lampson
ACM Operating Systems Review, 15:5, October 1983
Lampson, a famous systems researcher, loved using hints in the design of computer systems. A hint is something that is often correct but can be wrong; in this use, a signal() is telling a waiting thread that it changed the condition that the waiter was waiting on, but not to trust that the condition will be in the desired state when the waiting thread wakes up. In this paper about hints for designing systems, one of Lampson’s general hints is that you should use hints. It is not as confusing as it sounds.

prev|next