セマフォは、マルチスレッドの問題を解決するために頻繁に使用されるプログラミング概念です。コミュニティへの私の質問:
セマフォとは何ですか?どのように使用しますか?
セマフォは、マルチスレッドの問題を解決するために頻繁に使用されるプログラミング概念です。コミュニティへの私の質問:
セマフォとは何ですか?どのように使用しますか?
回答:
セマフォをナイトクラブの用心棒と考えてください。一度にクラブに入会できる献身的な人数がいます。クラブが満員の場合、誰も入場できませんが、1人が去るとすぐに別の人が入場する可能性があります。
これは単に、特定のリソースのコンシューマの数を制限する方法です。たとえば、アプリケーションのデータベースへの同時呼び出しの数を制限します。
これはC#での非常に教育的な例です:-)
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
namespace TheNightclub
{
public class Program
{
public static Semaphore Bouncer { get; set; }
public static void Main(string[] args)
{
// Create the semaphore with 3 slots, where 3 are available.
Bouncer = new Semaphore(3, 3);
// Open the nightclub.
OpenNightclub();
}
public static void OpenNightclub()
{
for (int i = 1; i <= 50; i++)
{
// Let each guest enter on an own thread.
Thread thread = new Thread(new ParameterizedThreadStart(Guest));
thread.Start(i);
}
}
public static void Guest(object args)
{
// Wait to enter the nightclub (a semaphore to be released).
Console.WriteLine("Guest {0} is waiting to entering nightclub.", args);
Bouncer.WaitOne();
// Do some dancing.
Console.WriteLine("Guest {0} is doing some dancing.", args);
Thread.Sleep(500);
// Let one guest out (release one semaphore).
Console.WriteLine("Guest {0} is leaving the nightclub.", args);
Bouncer.Release(1);
}
}
}
記事ミューテックスとセマフォDemystified by Michael Barrは、ミューテックスとセマフォの違いを説明し、それらを使用する必要がある場合と使用しない場合について説明しています。ここではいくつかの重要な段落を抜粋しました。
重要な点は、共有リソースを保護するためにミューテックスを使用する必要がある一方で、シグナリングにはセマフォを使用する必要があることです。一般に、共有リソースを保護するためにセマフォを使用したり、シグナリングのためにミューテックスを使用したりしないでください。たとえば、共有リソースを保護するためにセマフォを使用するという点で、警備員のアナロジーには問題があります。そのように使用できますが、バグの診断が困難になる可能性があります。
ミューテックスとセマフォの実装にはいくつかの類似点がありますが、常に異なる方法で使用する必要があります。
一番上にある質問に対する最も一般的な(それでも正しくない)答えは、ミューテックスとセマフォは非常に似ているということです。唯一の重要な違いは、セマフォが1よりも多くカウントできることです。ほぼすべてのエンジニアは、ミューテックスがコードの重要なセクション内で相互排除を確実にすることによって共有リソースを保護するために使用されるバイナリフラグであることを正しく理解しているようです。しかし、「カウントセマフォ」の使用方法を拡張するように求められると、ほとんどのエンジニア(自信の度合いのみが異なります)は、これらがいくつかの同等のリソースを保護するために使用されるという教科書の意見の一部を表現します。
...
この時点で、バスルームキーを共有リソース(バスルーム)を保護するという考え方を使用して、興味深いアナロジーが作成されます。ショップにバスルームが1つしかない場合、そのリソースを保護し、複数の人が同時に使用するのを防ぐには、1つのキーで十分です。
バスルームが複数ある場合、それらに同じようにキーを設定して複数のキーを作成したくなるかもしれません。これは、セマフォが誤って使用されていることに似ています。いったんキーを取得すると、どのバスルームが利用可能か実際にはわかりません。このパスをたどると、おそらくミューテックスを使用してその情報を提供し、すでに占有されているバスルームを使用しないようにします。 。
セマフォは本質的に同じリソースのいくつかを保護するための間違ったツールですが、これは多くの人々がそれを考えて使用する方法です。バウンサーのアナロジーは明らかに異なります。同じタイプのリソースはいくつかありませんが、複数の同時ユーザーを受け入れることができる1つのリソースがあります。セマフォはそのような状況で使用できると思いますが、類推が実際に当てはまる現実の状況はめったにありません-同じタイプのいくつかがまだありますが、使用できないバスルームなどの個別のリソースがまだありますこちらです。
...
セマフォの正しい使用法は、あるタスクから別のタスクへのシグナリング用です。mutexは、保護する共有リソースを使用する各タスクによって、常にこの順序で取得および解放されることを意図しています。対照的に、セマフォを使用するタスクは、シグナルまたは待機のいずれかを送信します。両方は送信しません。たとえば、タスク1には、「電源」ボタンが押されたときに特定のセマフォをポスト(つまり、シグナルまたはインクリメント)するコードが含まれ、タスク2はディスプレイをウェイクアップし、同じセマフォで待機します。このシナリオでは、1つのタスクはイベント信号のプロデューサーです。他の消費者。
...
ここで重要なポイントは、ミューテックスがリアルタイムのオペレーティングシステムに悪い方法で干渉し、リソースの共有のために重要度の低いタスクが重要度の高いタスクの前に実行される優先順位の逆転を引き起こすということです。つまり、これは、優先度の低いタスクがミューテックスを使用してリソースAを取得し、次にBを取得しようとしたが、Bが利用できないために一時停止した場合に発生します。待機中は、優先度の高いタスクが発生し、Aが必要になりますが、すでに拘束されており、Bを待機しているため実行されていないプロセスによっても発生しています。これを解決する方法はたくさんありますが、ほとんどの場合は修正されていますmutexとタスクマネージャを変更する。これらの場合、mutexはバイナリセマフォよりもはるかに複雑です。
...
ミューテックスとセマフォの間の現代の広範な混乱の原因は歴史的であり、それは1974年にジクストラによるセマフォ(この記事では大文字の「S」)の発明にまでさかのぼります。その日付以前は、コンピュータサイエンティストに知られている割り込みセーフタスク同期および信号メカニズムは、3つ以上のタスクで使用するために効率的に拡張できませんでした。ダイクストラの革新的で安全かつスケーラブルなセマフォは、クリティカルセクションの保護とシグナリングの両方に適用されました。そして混乱が始まった。
ただし、優先順位ベースのプリエンプティブRTOS(VRTXなど、1980年頃)の出現、RMAを確立する学術論文の公開、優先順位の逆転によって引き起こされる問題、および優先順位に関する論文の後に、オペレーティングシステムの開発者には後で明らかになりました。 1990年の継承プロトコル3では、ミューテックスはバイナリカウンターを備えたセマフォだけではないことが明らかになりました。
Mutex:リソース共有
セマフォ:シグナリング
副作用を注意深く考慮せずに、一方を他方に使用しないでください。
Mutex:リソースへの排他メンバーアクセス
セマフォ:リソースへのnメンバーのアクセス
つまり、ミューテックスを使用して、カウンター、ファイル、データベースなどへのアクセスを同期できます。
sempahoreも同じことができますが、一定数の同時発信者をサポートします。たとえば、データベースコールをsemaphore(3)でラップして、マルチスレッドアプリが最大3つの同時接続でデータベースにアクセスできるようにします。3つのスロットの1つが開くまで、すべての試行がブロックされます。彼らは素朴なスロットルをするようなことを本当に、本当に簡単にします。
@クレイグ:
セマフォはリソースをロックする方法であり、コードの一部が実行されている間、このコードのみがそのリソースにアクセスできることが保証されます。これにより、2つのスレッドが同時にリソースにアクセスできなくなり、問題が発生する可能性があります。
これは1つのスレッドだけに制限されません。セマフォは、一定数のスレッドがリソースにアクセスできるように構成できます。
並行プログラムの構築には、同期と相互排除という2つの重要な概念があります。これらの2種類のロック(セマフォは、より一般的には一種のロックメカニズム)が同期と相互排他を実現するのにどのように役立つかを見ていきます。
セマフォは、同期と相互排除の両方を実装することにより、並行性の実現を支援するプログラミング構造です。セマフォには、バイナリとカウントという2つのタイプがあります。
セマフォには2つの部分があります。カウンタと、特定のリソースへのアクセスを待機しているタスクのリストです。セマフォは2つの操作を実行します。待機(P)[これはロックの取得に似ています]と解放(V)[ロックの解放と同様]-これらは、セマフォに対して実行できる2つの操作のみです。バイナリセマフォでは、カウンタは論理的に0と1の間になります。これは、オープン/クローズの2つの値を持つロックに似ていると考えることができます。カウントセマフォには、countの値が複数あります。
理解しておくべき重要なことは、セマフォカウンタがブロックする必要のないタスクの数を追跡することです。つまり、タスクを進行させることができます。タスクはブロックし、カウンターがゼロの場合にのみ自分自身をセマフォのリストに追加します。したがって、タスクは、進行できない場合はP()ルーチンのリストに追加され、V()ルーチンを使用して「解放」されます。
これで、バイナリセマフォを使用して同期と相互排除を解決する方法を確認することはかなり明白です。これらは本質的にロックです。
例:同期:
thread A{
semaphore &s; //locks/semaphores are passed by reference! think about why this is so.
A(semaphore &s): s(s){} //constructor
foo(){
...
s.P();
;// some block of code B2
...
}
//thread B{
semaphore &s;
B(semaphore &s): s(s){} //constructor
foo(){
...
...
// some block of code B1
s.V();
..
}
main(){
semaphore s(0); // we start the semaphore at 0 (closed)
A a(s);
B b(s);
}
上記の例では、B2はB1が実行を終了した後にのみ実行できます。スレッドAが最初に実行されたとしましょう-カウンターが0(クローズ)であるため、sem.P()に到達して待機します。スレッドBが到着し、B1を完了してから、スレッドAを解放します。これにより、B2が完了します。したがって、同期を実現します。
次に、バイナリセマフォを使用した相互排除を見てみましょう。
thread mutual_ex{
semaphore &s;
mutual_ex(semaphore &s): s(s){} //constructor
foo(){
...
s.P();
//critical section
s.V();
...
...
s.P();
//critical section
s.V();
...
}
main(){
semaphore s(1);
mutual_ex m1(s);
mutual_ex m2(s);
}
相互排除も非常に簡単です。m1とm2は同時にクリティカルセクションに入ることができません。したがって、各スレッドは同じセマフォを使用して、2つのクリティカルセクションの相互排除を提供します。さて、同時実行性を高めることは可能ですか?クリティカルセクションによって異なります。(セマフォを使用して相互排除を実現する方法について考えてみてください。ヒントヒント:セマフォを1つだけ使用する必要がありますか?)
セマフォのカウント:複数の値を持つセマフォ。これが何を意味しているのか見てみましょう-複数の値を持つロック?? オープン、クローズ、そして...うーん。相互排除または同期におけるマルチステージロックの用途は何ですか。
2つのうちの簡単な方を見てみましょう。
カウントセマフォを使用した同期:3つのタスクがあるとします-#1と2を3の後に実行しますか?同期をどのように設計しますか?
thread t1{
...
s.P();
//block of code B1
thread t2{
...
s.P();
//block of code B2
thread t3{
...
//block of code B3
s.V();
s.V();
}
したがって、セマフォがクローズ状態で開始する場合は、t1およびt2ブロックがセマフォのリストに追加されることを確認します。次に、すべての重要なt3がやって来て、ビジネスを終了し、t1とt2を解放します。それらはどの順序で解放されますか?セマフォのリストの実装に依存します。FIFOの場合もあれば、特定の優先順位に基づく場合もあります。(注:t1とt2を特定の順序で実行したい場合、およびセマフォの実装を認識していない場合は、PとVをどのように配置するかを検討してください)
(検索:Vの数がPの数より大きい場合はどうなりますか?)
カウントセマフォを使用した相互排除:このために独自の疑似コードを作成してください(理解しやすくなります)。ただし、基本的な概念は次のとおりです。counter= Nのカウントセマフォにより、N個のタスクがクリティカルセクションに自由に入ることができます。 。つまり、N個のタスク(または、必要に応じてスレッド)がクリティカルセクションに入りますが、N + 1番目のタスクはブロックされ(お気に入りのブロックされたタスクのリストに移動します)、誰かVがセマフォであるときにのみ許可されます。少なくとも一度は。そのため、セマフォカウンターは0と1の間で変動するのではなく、0とNの間で移動するようになり、N個のタスクが自由に出入りできるようになり、誰もブロックしません!
さて、どうしてそんなばかげたものが必要なのでしょうか。複数の人がリソースにアクセスできないようにする相互排除の全体のポイントではないですか?(ヒントヒント...コンピュータにドライブが1つしかないわけではありません...?)
考えてみよう:セマフォを数えるだけで相互排除は実現できるのか リソースのインスタンスが10個あり、10個のスレッドが(カウントセマフォを通じて)入って、最初のインスタンスを使用しようとした場合はどうなりますか?
セマフォは、2つの変更操作が定義されている自然数(つまり、ゼロ以上の整数)を含むオブジェクトです。1つの演算、V
自然に1を追加します。他の操作はP
、自然数を1減らします。両方のアクティビティはアトミックです(つまり、a V
またはa と同時に他の操作を実行することはできませんP
)。
自然数0を減らすことはできないため、P
0を含むセマフォを呼び出すと、数が0でなくなりP
正常に(そしてアトミックに)実行できるようになるまで、呼び出しプロセス(/スレッド)の実行がブロックされます。
他の回答で述べたように、セマフォを使用して、特定のリソースへのアクセスをプロセスの最大数(ただし可変)に制限できます。
アイデアを理解するのに役立つ視覚化を作成しました。セマフォは、マルチスレッド環境での共通リソースへのアクセスを制御します。
ExecutorService executor = Executors.newFixedThreadPool(7);
Semaphore semaphore = new Semaphore(4);
Runnable longRunningTask = () -> {
boolean permit = false;
try {
permit = semaphore.tryAcquire(1, TimeUnit.SECONDS);
if (permit) {
System.out.println("Semaphore acquired");
Thread.sleep(5);
} else {
System.out.println("Could not acquire semaphore");
}
} catch (InterruptedException e) {
throw new IllegalStateException(e);
} finally {
if (permit) {
semaphore.release();
}
}
};
// execute tasks
for (int j = 0; j < 10; j++) {
executor.submit(longRunningTask);
}
executor.shutdown();
出力
Semaphore acquired
Semaphore acquired
Semaphore acquired
Semaphore acquired
Could not acquire semaphore
Could not acquire semaphore
Could not acquire semaphore
記事のサンプルコード
セマフォはスレッドリミッタのように機能します。
例: 100スレッドのプールがあり、DB操作を実行したい場合。一度に100スレッドがDBにアクセスする場合、DBにロックの問題がある可能性があるため、一度に制限されたスレッドのみを許可するセマフォを使用できます。以下の例では、一度に1つのスレッドのみを許可します。スレッドはacquire()
メソッドを呼び出すとアクセスを取得し、release()
メソッドを呼び出した後、アクセスを解放して次のスレッドがアクセスを取得できるようにします。
package practice;
import java.util.concurrent.Semaphore;
public class SemaphoreExample {
public static void main(String[] args) {
Semaphore s = new Semaphore(1);
semaphoreTask s1 = new semaphoreTask(s);
semaphoreTask s2 = new semaphoreTask(s);
semaphoreTask s3 = new semaphoreTask(s);
semaphoreTask s4 = new semaphoreTask(s);
semaphoreTask s5 = new semaphoreTask(s);
s1.start();
s2.start();
s3.start();
s4.start();
s5.start();
}
}
class semaphoreTask extends Thread {
Semaphore s;
public semaphoreTask(Semaphore s) {
this.s = s;
}
@Override
public void run() {
try {
s.acquire();
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+" Going to perform some operation");
s.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
だから、誰もがトイレに行こうとしていると想像してください。残りのキーが足りない場合、その人は待つ必要があります。したがって、セマフォを、さまざまなプロセス(バスルームの常駐者)がアクセスを要求できるバスルーム(システムリソース)で使用できるキーのセットを表すと考えてください。
次に、2つのプロセスが同時にトイレに行こうとしているところを想像してください。これは良い状況ではなく、これを防ぐためにセマフォが使用されます。残念ながら、セマフォは自発的なメカニズムであり、プロセス(私たちの浴室の常連客)はそれを無視できます(つまり、キーがあったとしても、誰かがドアを開けるだけです)。
バイナリ/ミューテックスとセマフォのカウントにも違いがあります。
http://www.cs.columbia.edu/~jae/4118/lect/L05-ipc.htmlで講義ノートを確認してください。