前書き
オープニング豆知識
真面目な話、今回は張本サンが一緒に復習し、プロセス間コミュニケーションの理解を深めて、次回はより長い時間エアコンを吹かせることができるようにします。
主な記事
各プロセスのユーザーアドレス空間は別々で、一般的に互いにアクセスできませんが、カーネル空間はすべてのプロセスで共有されているため、プロセス同士が通信するにはカーネルを経由する必要があります。
Linuxカーネルはプロセス間通信のための多くのメカニズムを提供しています。
パイプライン
Linuxのコマンドを習ったことがある人なら、""の縦線はおなじみでしょう。
$ ps auxf | grep mysql
上のコマンドラインの「縦線」はパイプラインで、その機能は前のコマンドの出力を次のコマンドの入力として取り込むことです。この機能の説明から、パイプラインのデータ転送は一方向であることがわかりますが、お互いに通信したい場合は、2つのパイプラインを作成する必要があります。
同時に、上記のようなパイプラインには名前がないことが知られているため、" "で示されるパイプラインは匿名パイプラインと呼ばれ、使い切ると破棄されます。
パイプのもう一つのタイプは名前付きパイプで、データがFIFOで転送されることからFIFOとも呼ばれます。
名前付きパイプを使用する前に、mkfifoコマンドでパイプを作成し、パイプ名を指定する必要があります:
$ mkfifo myPipe
myPipeはパイプの名前です。 Linuxではすべてがファイルであるという考え方に基づき、パイプはファイルとして存在します:
$ ls -l
prw-r--r--. 1 root root 0 Jul 17 02:45 myPipe
次に、パイプ myPipe にデータを書き込みます:
$ echo "hello" > myPipe // パイプラインにデータを書き込む
// とまれ。 ...
これはパイプラインの内容が読み込まれていないためで、パイプラインのデータが読み込まれて初めて、コマンドは正常に終了します。
そこで、別のコマンドを実行して、このパイプ内のデータを読み込みます:
$ cat < myPipe // パイプラインからデータを読み込む
hello
ご覧のように、パイプの内容は読み込まれ、ターミナルに表示されます。一方、echoコマンドは正常に終了します。
お分かりのように、パイピングは非効率的な通信方法であり、プロセス間で頻繁にデータを交換するのには適していません。もちろん、単純で、パイプ内のデータが他のプロセスによって読み取られたことを簡単に知ることができるという利点はあります。
では、パイプラインはどのようにして作られるのでしょうか?
匿名パイプラインは、以下のシステムコールで作成されます:
int pipe(int fd[2])
これは、匿名パイプが作成され、2つのディスクリプタが返されることを意味します。1つはパイプの読み取り側のディスクリプタfd[0]、もう1つはパイプの書き込み側のディスクリプタfd[1]です。この匿名パイプは、ファイルシステム上ではなく、メモリ上にのみ存在する特別なファイルであることに注意してください。
実際には、いわゆるパイプラインはカーネル内のキャッシュの列です。パイプの一端から書き込まれたデータは実際にはカーネル内にキャッシュされ、もう一端から読み込まれたデータはカーネルから読み込まれます。また、パイプで転送されるデータはフォーマットされていないストリームであり、サイズに制限があります。
これを見て、あなたは疑問があるかもしれません、これらの2つの記述子は、プロセスの内部にあり、プロセス間通信の役割を果たしません、どのようにパイプラインは、2つのプロセスをまたぐようにするには?
forkを使用して子プロセスを作成し、その子プロセスが親プロセスのファイル記述子をコピーすることで、2つのプロセスがそれぞれ2つの「fd[0]とfd[1]」を持ち、2つのプロセスがそれぞれのfdを介して同じパイプラインファイルを読み書きすることで、クロスプロセス通信を実現することができます。
パイプは片方からしか書き込めず、もう片方からしか読み込めないため、上記のパターンでは、親プロセスと子プロセスの両方が同時に書き込みと読み込みを行うことになり、混乱が生じやすくなります。そのため、このような事態を避けるために、通常は次のようにします:
- 親プロセスは読み出し用にfd[0]を閉じ、書き込み用にfd[1]のみを保持します;
- 子プロセスは書き込み用に fd[1] を閉じ、読み込み用に fd[0] だけを保持します;
つまり、双方向通信が必要な場合は、2本のパイプを作成する必要があります。
ここまでは、親プロセスと子プロセス間の通信にパイプを使用することだけを解析してきましたが、シェル内部ではそうはいきません。
シェル内部でA|Bコマンドを実行する場合、AプロセスもBプロセスもシェルが生成した子プロセスであり、AとBの間に親子関係はありません。
つまり、シェルで複数のコマンドを匿名パイプでつなぐと、実際には複数のサブプロセスを作成していることになるので、シェルスクリプトを書くときは、できる限り1つのパイプですべてを処理しないようにすると、サブプロセスを作成するオーバーヘッドを減らすことができます。
匿名パイプラインの通信範囲は、親子関係が存在するプロセスであることがわかります。パイプは実体を持たないため、つまりパイプファイルが存在しないため、親プロセスの fd ファイル記述子を fork してコピーすることでしか通信の目的を達成できません。
また、名前付きパイプでは、無関係なプロセス同士でも通信が可能です。コマンドパイプのため、パイプ型のデバイスファイルがあらかじめ作成されており、このデバイスファイルを使っている限り、プロセス同士の通信が可能です。
匿名パイプラインや名前付きパイプラインに関係なく、プロセスによって書き込まれたデータはカーネルにキャッシュされ、別のプロセスはカーネルからデータを自然に読み込みます。
メッセージキュー
パイプライン方式の通信は非効率であるため、パイプラインはプロセス間の頻繁なデータ交換には適さないことは前述しました。
この問題を解決するには、メッセージキュー通信モードが有効です。例えば、AプロセスがBプロセスにメッセージを送りたい場合、Aプロセスは対応するメッセージキューにデータを入れて普通に戻り、Bプロセスは必要な時にデータを読みます。同様に、BプロセスはAプロセスにメッセージを送ることができます。
繰り返しますが、メッセージ・キューはカーネルに格納されたメッセージの連鎖表であり、データを送信する際には、1つずつ独立したデータ単位、つまりユーザー定義のデータ型であるメッセージ・ボディに分割されます。メッセージの送信者と受信者は、メッセージ・ボディのデータ型に合意しなければならないため、各メッセージ・ボディは、データの書式なしバイト・ストリームであるパイプとは異なり、固定サイズのストレージ・ブロックです。あるプロセスがメッセージ・キューからメッセージ・ボディを読み取ると、カーネルはそのメッセージ・ボディを削除します。
メッセージキューのライフサイクルはカーネルに従います。メッセージキューが解放されなかったり、オペレーティングシステムがシャットダウンされなかったりすると、メッセージキューは常にそこに存在することになります。
このメッセージモデルでは、2つのプロセス間の通信は通常の電子メールのようなものです。
ひとつはタイムリーな通信ができないこと、もうひとつは添付ファイルのサイズに制限があることです。
メッセージキューは、より大きなデータ転送には適していません。なぜなら、カーネル内の各メッセージボディの長さには上限があり、またすべてのキューに含まれるすべてのメッセージボディの長さの合計にも上限があるからです。Linux カーネルには MSGMAX と MSGMNB という 2 つのマクロがあり、それぞれメッセージの最大長とキューの最大長をバイト単位で定義しています。
メッセージ・キュー通信中、あるプロセスがカーネル内のメッセージ・キューにデータを書き込むと、ユーザ状態からカーネル状態にデータをコピーする処理が発生し、同様に、別のプロセスがカーネル内のメッセージ・データを読み込むと、カーネル状態からユーザ状態にデータをコピーする処理が発生するため、ユーザ状態とカーネル状態の間でデータ・コピーのオーバーヘッドが発生します。
共有メモリ
メッセージ・キューへの読み書きのプロセスでは、ユーザー状態とカーネル状態の間でメッセージをコピーするプロセスが発生します。そこで、共有メモリ・アプローチは、この問題に対する良い解決策となります。
つまり、各プロセスはそれぞれ独立した仮想メモリ空間を持ち、異なるプロセスの仮想メモリは異なる物理メモリにマッピングされます。したがって、プロセスAとプロセスBの仮想アドレスが同じであっても、実際には異なる物理メモリアドレスにアクセスしており、データの追加、削除、チェック、変更は互いに影響しません。
共有メモリのメカニズムは、仮想アドレス空間の一部を取り出し、同じ物理メモリにマッピングすることです。このプロセスは何かを書き込むと、別のプロセスはすぐにそれを見ることができ、コピーしてコピーする必要はありません、大幅にプロセス間通信の速度を向上させ、周りに渡します。
シグナル
つまり、複数のプロセスが同じ共有メモリを同時に変更した場合、競合が発生する可能性が非常に高いのです。例えば、2つのプロセスが同時にあるアドレスに書き込んだ場合、先に書き込んだプロセスは、その内容が他の誰かによって上書きされていることに気づくでしょう。
複数のプロセスが共有リソースを奪い合ってデータの不一致を起こさないようにするには、共有リソースにいつでも1つのプロセスしかアクセスできないようにする保護メカニズムが必要です。セマフォはこの保護機構を実装しています。
セマフォは実際には整数カウンタであり、プロセス間通信のデータをキャッシュするためではなく、プロセス間の相互排除と同期を実装するために主に使用されます。
セマフォはリソースの量を示し、セマフォを制御するアトミック操作は2つあります:
- < 0,则表明资源已被占用,进程需阻塞等待;相减后如果信号量 >1つは P操作で、セマフォから1を減算します。減算後にセマフォが0であれば、まだ利用可能なリソースがあり、プロセスは正常に実行し続けることができます。
- <= 0,则表明当前有阻塞中的进程,于是会将该进程唤醒运行;相加后如果信号量 > もう1つは V操作で、セマフォに1が追加され、セマフォに0が追加されれば、ブロッキング処理は発生しません;
P操作は共有リソースに入るときに使い、V操作は共有リソースから出るときに使います。
次に、例として、2つのプロセスが共有メモリにアクセスするのを相互に排他的にしたい場合、セマフォを.
具体的なプロセスは以下の通り:
- プロセスAは共有メモリにアクセスする前にP操作を行います。 信号量の初期値は1であるため、プロセスAがP操作を行うと信号量は0となり、共有リソースが利用可能になったことを示すので、プロセスAは共有メモリにアクセスできます。
- プロセスBも共有メモリにアクセスしようとしてP操作を行うと、シグナルレベルが-1になり、クリティカルリソースが占有されたことになり、プロセスBはブロックされます。
- プロセスAは共有メモリへのアクセスが終了するまで、Vオペレーションを実行してシグナル・ボリュームを0に戻します。次に、プロセスBが共有メモリにアクセスできるように、ブロックしているスレッドBをウェイクアップし、共有メモリへのアクセスが終了したら、最後にVオペレーションを実行してシグナル・ボリュームを初期値1に戻します。
に初期化されたシグナルは、相互に排他的なセマフォであることを意味し、任意の瞬間に1つのプロセスだけがアクセスするため、共有メモリが確実に保護されることがわかります。
また、マルチプロセッシングでは、各プロセスは必ずしも逐次的に実行されるわけではなく、基本的にはそれぞれ独立した予測不可能なペースで進みますが、複数のプロセスが緊密に連携して共通のタスクを達成することが望ましい場合もあります。
例えば、プロセスAはデータを生成する責任があり、プロセスBはデータを読み取る責任があり、これらの2つのプロセスは相互に協力し、相互依存しています。
このときこそ、マルチプロセス同期を実装し、シグナルを.
具体的なプロセス
- プロセスAより先にプロセスBが実行された場合、プロセスPが実行されると、信号量の初期値は0なので、信号量は-1になり、プロセスAはまだデータを生成していないので、プロセスBはブロックして待ちます;
- そして、プロセスAはデータの生成が終わると、信号量を0にするVオペレーションを行い、PオペレーションをブロックしているプロセスBをウェイクアップさせます;
- 最後に、プロセスBがウェイクアップされるということは、プロセスAがデータを生成したということなので、プロセスBは普通にデータを読むことができます。
に初期化されたシグナルは、同期セマフォであることを意味し、プロセスAがプロセスBで実行されることを保証していることがわかります。
シグナル
上記のプロセス間通信は、通常時の作業モードです。異常時の作業モードは、「シグナル」によってプロセスに通知する必要があります。
シグナルとセマフォは、名前こそ66.66%似ていますが、JavaとJavaScriptの違いのように、用途は全く異なります。
Linuxオペレーティング・システムでは、さまざまなイベントに応じて数十のシグナルが提供され、それぞれが異なる意味を持ちます。kill -lコマンドですべてのシグナルを見ることができます:
$ kill -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX
シェル端末で実行中のプロセスは、キーボードから特定のキーの組み合わせが入力されると、そのプロセスにシグナルを送ることができます。例えば
- Ctrl+Cは、プロセスの終了を示すSIGINTシグナルを生成します;
- Ctrl+ZはSIGTSTPシグナルを生成し、プロセスが停止しているがまだ終了していないことを示します;
プロセスがバックグラウンドで実行されている場合、killコマンドによってプロセスにシグナルを送ることができますが、これは実行中のプロセスのPID番号がわかっている場合などに限られます:
- kill -9 1050 は、PID 1050 のプロセスに SIGKILL シグナルを送り、プロセスを直ちに終了させることを意味します;
したがって、信号イベントの発生源は、主にハードウェア・ソースとソフトウェア・ソースです。
シグナルは、プロセス間通信メカニズムの中で唯一の非同期通信メカニズムです。なぜなら、シグナルはいつでもプロセスに送ることができ、シグナルが生成されると、ユーザープロセスがシグナルを処理する方法は以下の通りです。
1.デフォルトのアクションを実行するLinuxでは、各シグナルに対してデフォルトのアクションが指定されています。例えば、上のリストにあるSIGTERMシグナルは、プロセスの終了を意味します。
2.信号をキャプチャします。信号に対して信号処理関数を定義することができます。信号が発生すると、対応する信号処理関数が実行されます。
3.信号の無視特定のシグナルを処理したくない場合、シグナルを無視して何もしないことが可能です。SIGKILL と SEGSTOP という 2 つのシグナルがあり、これはいつでもプロセスを中断または終了するために使用されます。
Socket
先に述べたパイプ、メッセージキュー、共有メモリ、セマフォ、シグナルは、すべて同一ホスト上のプロセス間通信に使用されます。次に、異なるホスト上のプロセスとネットワークを介して通信するには、ソケット通信が必要です。
実際、ソケット通信は、ネットワークや異なるホスト間でのプロセス間通信だけでなく、同じホスト上でのプロセス間通信にも使用できます。
ソケット作成システムコールを見てみましょう:
int socket(int domain, int type, int protocal)
この3つのパラメータは
- domain パラメータは、IPV4 の場合は AF_INET、IPV6 の場合は AF_INET6、local の場合は AF_LOCAL/AF_UNIX のようにプロトコルファミリを指定します;
- 例えば、SOCK_STREAM は TCP に対応するバイトストリーム、SOCK_DGRAM は UDP に対応するデータグラム、SOCK_RAW は raw ソケットを意味します;
- protocalパラメータはもともと通信プロトコルを指定するために使用されていましたが、現在では基本的に非推奨となっています。プロトコルは前の2つのパラメータですでに指定されているため、現在では一般的にprotocolは0と記述されます;
ソケットの種類によって、コミュニケーションの方法は異なります:
- TCP バイトストリーム通信の実装: ソケットタイプは AF_INET と SOCK_STREAM です;
- UDP データグラム通信の実装: ソケットタイプは AF_INET と SOCK_DGRAM です;
- ローカルなプロセス間通信を実現するために: "local byte stream socket" の型は AF_LOCAL と SOCK_STREAM であり、 "local datagram socket" の型は AF_LOCAL と SOCK_DGRAM です;
次に、3つのプログラミング通信モードについて簡単に説明します。
TCPプロトコル通信のためのソケットプログラミングモデル
- サーバとクライアントはソケットを初期化し、ファイルディスクリプタを取得します;
- サーバーはbindを呼び出し、IPアドレスとポートにバインドします。
- サーバーは listen を listen と呼びます;
- サーバーはacceptを呼び出し、クライアントが接続するのを待ちます;
- クライアントはconnectを呼び出し、サーバー側のアドレスとポートへの接続要求を開始します;
- Server accept は、転送に使用されたソケットのファイルディスクリプタを返します;
- クライアントはデータを書き込むためにwriteを呼び、サーバーはデータを読み込むためにreadを呼びます;
- クライアントが接続を切断するとcloseが呼び出され、サーバーがデータを読み込むとEOFが読み込まれ、データが処理されるとcloseが呼び出され、接続が切断されたことが示されます。
重要なのは、サーバーがacceptを呼び出したとき、接続に成功すると完了したソケットが返され、そのソケットは後でデータ転送に使用されるということです。
したがって、リスニングソケットと実際にデータ転送を行うソケットは「2つ」のソケットとなり、一方をリスニングソケット、もう一方を接続完了ソケットと呼びます。
接続が成功すると、ファイルストリームに書き込むのと同じように、双方とも読み取り関数と書き込み関数を使ってデータの読み取りと書き込みを開始します。
UDPプロトコル通信のためのソケットプログラミングモデル
UDPは接続されないので、3回のハンドシェイクは必要なく、TCPのようにlistenとconnectを呼び出す必要もありませんが、UDPの相互作用は依然としてIPアドレスとポート番号を必要とし、したがってバインドを必要とします。
UDPの場合、コネクションを維持する必要がないため、送信者と受信者というものが存在せず、クライアントとサーバーという概念すら存在しません。
さらに、sendtoとrecvfromの通信呼び出しごとに、ターゲット・ホストのIPアドレスとポートが渡されます。
ローカルプロセス間通信のためのソケットプログラミングモデル
ローカル・ソケットは、同じホスト上のプロセス間で通信を行う場合に使用されます:
- ローカルソケットのプログラミングインターフェースはIPv4やIPv6ソケットと同じで、「バイトストリーム」と「データグラム」の両方のプロトコルをサポートできます;
- ローカルソケットの実装は、IPv4やIPv6のバイトストリーム、データグラムソケットの実装よりもはるかに効率的です;
ローカルバイトストリームソケットのソケットタイプは AF_LOCAL と SOCK_STREAM です。
ローカルデータグラムソケットでは、ソケットタイプは AF_LOCAL と SOCK_DGRAM です。
ローカル・バイト・ストリーム・ソケットとローカル・データグラム・ソケットは、TCPやUDPとは異なり、IPアドレスやポートではなくローカル・ファイルにバインドします。
まとめ
各プロセスのユーザー空間は独立しており、互いにアクセスできないため、各プロセスがカーネル空間を共有するという単純な理由から、カーネル空間の助けを借りてプロセス間通信を実装する必要があります。
Linuxカーネルは、プロセス間で通信するための多くの方法を提供しています。その中で最も単純なものがパイプで、「匿名パイプ」と「名前付きパイプ」に分かれています。
匿名パイプラインは、その名が示すように、それは識別するための名前を持っていない、匿名パイプラインは、メモリ上にのみ存在する特殊なファイルであり、ファイルシステムには存在しません、"縦の行 "のシェルコマンドは、匿名パイプラインであり、通信データは、フォーマットのないストリームであり、制限されたサイズは、通信は一方通行であり、データは一方向にのみ流すことができる、あなたは両方向に通信したい場合は、2つのパイプラインを作成する必要があり、その後、匿名パイプラインは、次のとおりです。親子関係がある場合にのみプロセス間通信に使用できます。 匿名パイプラインのライフサイクルは、プロセスの作成とともに確立され、プロセスの終了とともに消滅します。
名前付きパイプは、匿名パイプが関連するプロセス間の通信にしか使えないという制約を破るもので、名前付きパイプを使う前提条件として、ファイルシステムにp型のデバイスファイルを作成し、このデバイスファイルを介して関連しないプロセス同士が通信できるようになります。また、匿名パイプラインでも名前付きパイプラインでも、プロセスが書き込んだデータはカーネルにキャッシュされ、別のプロセスがカーネルからデータを読み出すのは自然なことであり、同時に通信データは先入れ先出しの原則に従い、lseekなどのファイルロケーション操作はサポートしていません。
メッセージキューは、パイプライン通信のデータがフォーマットされていないバイトストリームであるという問題を克服し、メッセージキューは、実際にはカーネルの "メッセージチェーンテーブル "に保存され、メッセージキューのメッセージボディは、データを送信するときに1つずつ独立したメッセージボディに分割することができるユーザー定義可能なデータ型であり、もちろん、データを受信するとき、データ型は、読み取りデータが正しいことを保証するために、送信者が送信したメッセージボディと一致している必要があります。データは正しいメッセージキュー通信の速度は、最もタイムリーではありません。結局のところ、データが書き込まれ、読み込まれるたびに、ユーザー状態とカーネル状態の間でコピープロセスを通過する必要があります。
共有メモリは、メッセージキュー通信におけるユーザー状態とカーネル状態間のデータコピー処理のオーバーヘッドを解決することができ、カーネル状態やシステムコールに陥ることなく、できるだけ迅速かつ便利にプロセス自身の空間にアクセスするのと同じように、各プロセスが直接アクセスできる共有空間を直接割り当て、通信速度を大幅に向上させ、プロセス間通信の最速の方法の名前を楽しむことができます。しかし、共有メモリ通信の利便性と効率性は、複数のプロセスが同じ共有リソースを奪い合うことで、データの不一致を引き起こすという新たな問題をもたらします。
セマフォは、実際にはリソースの数を表すカウンタであり、P演算とP演算という2つのアトミック演算によって値を制御することができます。セマフォは実際にはリソースの数を表すカウンタであり、その値は P演算とV演算という2つのアトミック演算によって制御することができます。
セマフォの名前はコールシグナルに非常に似ていると、それは2つの名前は似ていますが、機能は全く同じではありません。シグナルは、プロセス間の唯一の非同期通信メカニズムであり、シグナルは直接アプリケーションプロセスとカーネルの間で相互作用することができ、カーネルはまた、ユーザー空間プロセスに通知するためにシグナルを使用することができますどのようなシステムイベントが発生し、信号イベントのソースは、主にハードウェアとソフトウェアのソースであり、一度信号が発生すると、プロセスは、信号に応答する3つの方法があります1.デフォルトの操作を実行し、2.信号をキャプチャし、3.信号を無視します。シグナルが発生すると、プロセスには3つの対応方法があります。SIGKILL と SEGSTOP という、アプリケーション・プロセスがキャプチャも無視もできない 2 つのシグナルがあります。
Socketは実は異なるホスト上のプロセス間通信に使われるだけでなく、ローカルホスト上のプロセス間通信にも使うことができ、作成されるSocketの種類によって、TCPプロトコルに基づくもの、UDPプロトコルに基づくもの、ローカルプロセス間通信モードに基づくものの3つの一般的な通信モードに分類することができます。ソケット通信は、異なるホストプロセス間の通信だけでなく、ローカルホストプロセス間の通信にも使用されます。
とりわけ、これがプロセス間通信の主なメカニズムです。スレッド間の通信はどうなっているのですか?
同じプロセス下のスレッドは、共有変数がスレッド間で通信するために行うことができる限り、グローバル変数などのプロセスリソースを共有しているので、スレッド間の懸念は、通信の方法ではなく、共有リソースのマルチスレッド競争の問題に焦点を当て、信号量はまた、相互排除と同期を達成するためにスレッド間で同じにすることができます:
- 相互排他は、任意の瞬間に1つのスレッドだけが共有リソースにアクセスすることを保証します;
- 同期アプローチは、スレッドAがスレッドBによって実行されることを保証します;
さて、本日の張本評は以上です。張本さんには早く心のオファーを受けて、汗まみれで夏を締めくくってほしいものです。




