おそらく多くの友人は、NIO を学習するときに少し難しいと感じるでしょうし、NIO の概念の多くはそれほど明確ではありません。 Java NIOプログラミングに入る前に、今日はI/Oモデルという基本的な知識について説明しましょう。次の記事では、同期と非同期の概念から始まり、ブロッキングと非ブロッキングの違いを説明し、次にブロッキング IO と非ブロッキング IO の違いを紹介し、次に同期 IO と非同期 IO の違いを紹介します。 5 つの IO モデル、そして最後に高性能 IO 設計に関連する 2 つの設計パターン (Reactor と Proactor) が紹介されています。
以下はこの記事の目次の概要です:
1. 同期とは何ですか?非同期とは何ですか?
2. ブロックとは何ですか?ノンブロッキングとは何ですか?
3. ブロッキングIOとは何ですか?ノンブロッキングIOとは何ですか?
4. 同期 IO とは何ですか?非同期IOとは何ですか?
5. 5 つの IO モデル
6. 2 つの高性能 IO 設計パターン
もし間違いがあれば、ご容赦ください。批判や修正を歓迎します。
同期と非同期の概念は古くから存在しており、インターネット上でも同期と非同期について多くの意見が存在します。以下は私の個人的な理解です:
同期とは、複数のタスクまたは イベント が発生する場合、これらのタスクまたはイベントを 1 つずつ実行する必要があり、1 つのイベントまたはタスクの実行によりプロセス全体が待機することを意味します。これらのイベントは一時的に実行されません。メソッドは同時に実行できます。
非同期とは、複数のタスクまたはイベントが発生した場合に、これらのイベントを同時に実行でき、1 つのイベントまたはタスクの実行によってプロセス全体が一時的に待機することはありません。
これは同期と非同期です。簡単な例を挙げると、2 つのサブタスク A と B を含むタスクがあるとします。同期の場合、A が実行されている場合、B は A が完了するまで待機してから B を実行できます。非同期の場合、A と B は実行できます。また、B は A の実行が完了するまで待つ必要がないため、A の実行によってタスク全体が一時的に待機することはありません。
それでも理解できない場合は、まず次の 2 つのコードを読んでください。
5
6
7
9
12 13 14 void fun1() {
}<p class="line number9 index8 alt2"></p>
<code class="java space">
void code> <code class="java plain">fun2() {
}
void code> function(){
|
fun1();
|
このコードは、メソッド関数では、fun1 を実行すると、後続の fun2 が実行できなくなり、fun1 の実行が完了するまで待つ必要があります。
次に、次のコードを見てください。 6
7
9
10
131415161718192021 222324
}
|
}
|
public
void
run() {
🎜🎜
fun2();
🎜 🎜
}
🎜🎜
}.start();
🎜🎜 🎜🎜
....
🎜🎜 ....
🎜🎜}
🎜🎜🎜🎜 🎜このコードは典型的な非同期であり、fun1 の実行は fun2 の実行に影響を与えず、fun1 と fun2 の実行によって後続の実行プロセスが一時的に待機することはありません。
実際、同期と非同期は非常に幅広い概念であり、複数のタスクやイベントが発生したときに、イベントの発生または実行によってプロセス全体が一時的に待機するかどうかに焦点が当てられています。同期と非同期は、Java の synchronized キーワードで類推できると思います。複数のスレッドが同時に 変数 にアクセスする場合、各スレッドの変数へのアクセスは同期のために、1 つのスレッドが変数にアクセスするプロセス中に、他のスレッドが 1 つずつ変数にアクセスする必要があります。 wait; 非同期の場合、複数のスレッドが変数に 1 つずつアクセスする必要はなく、同時にアクセスできます。
したがって、同期と非同期はさまざまな方法で表現できると個人的に感じていますが、覚えておくべき鍵は、複数のタスクやイベントが発生したときに、イベントの発生または実行によってプロセス全体が一時的に待機するかどうかです。一般に、非同期はマルチスレッドによって実現できますが、非同期は単なるマクロ パターンであり、マルチスレッドを使用して非同期を実現することは単なる手段であり、非同期の実装も実現できることに注意してください。マルチプロセッシングを通じて。
同期と非同期の違いについては前に紹介しましたが、このセクションではブロッキングとノンブロッキングの違いを見てみましょう。
ブロッキングとは、イベントやタスクの実行時にリクエスト操作を発行するが、リクエスト操作に必要な条件が満たされていないため、条件が満たされるまでそこで待機することを意味します。イベントまたはタスクの実行中にリクエスト操作を発行すると、リクエスト操作に必要な条件が満たされていない場合、条件が満たされていないことを通知するフラグ メッセージが即座に返され、そこで永遠に待機することはありません。 。
これがブロックするかしないかの違いです。つまり、ブロッキングと非ブロッキングの主な違いは、操作のリクエストが行われたときに、条件が満たされない場合に、永久に待機するかフラグ メッセージを返すかということです。
簡単な例を挙げると:
ファイルの内容を読みたい場合、その時点でファイル内に読み取り可能な内容がなければ、同期のために、ファイル内に読み取り可能な内容ができるまでそこで待機します。ノンブロッキングの場合、読み込むファイルにコンテンツがないことを通知するフラグ メッセージが直接返されます。
インターネット上の友人の中には、同期と非同期をそれぞれブロッキングと非ブロッキングと同一視する人もいます。実際、これらは 2 つのまったく異なる概念です。これら 2 つの概念セットの違いを理解することは、その後の IO モデルを理解する上で非常に重要であることに注意してください。
同期と非同期の焦点は、1 つのタスクの実行によって、複数のタスクの実行中にプロセス全体が一時的に待機するかどうかにあります。
ブロックと非ブロックの焦点は、リクエスト操作を発行するときに当てられます。操作の条件が満たされていない場合は、条件が満たされていないことを通知するフラグ メッセージが返されます。
ブロッキングとノンブロッキングを理解することは、スレッドがリクエスト操作を実行するとき、条件が満たされない場合はブロックされ、つまり条件が満たされるのを待つことと同様に理解できます。
3. ブロッキング IO とは何ですか?ノンブロッキングIOとは何ですか?
一般的に、IO 操作には、ハードディスクへの読み取りと書き込み、ソケットへの読み取りと書き込み、周辺機器への読み取りと書き込みが含まれます。
ユーザー スレッドが IO リクエスト オペレーションを開始すると (この記事では読み取りリクエスト オペレーションを例にします)、カーネルは、読み取られるデータの準備ができているかどうかを確認し、データの準備ができていない場合は IO をブロックします。データの準備ができるまで常にそこで待機します。ノンブロッキング IO の場合、データの準備ができていない場合は、現在読み取られているデータの準備ができていないことをユーザー スレッドに通知するフラグ メッセージが返されます。データの準備ができたら、データがユーザー スレッドにコピーされ、完全な IO 読み取りリクエスト操作が完了します。つまり、完全な IO 読み取りリクエスト操作には 2 つの段階が含まれます:
1) データが準備されているかどうかを確認します。準備完了;
2) データをコピーします (カーネルはデータをユーザー スレッドにコピーします)。
それでは、ブロッキング(ブロッキングIO)とノンブロッキング(ノンブロッキングIO)の違いは、最初の段階で、データの準備ができていない場合、データが準備できているかどうかを確認する処理でずっと待つかどうかです。準備完了、またはフラグ情報を直接返すこともできます。
Java の従来の IO は、ソケットを介したデータの読み取りなどのブロック IO であり、read() メソッドを呼び出した後、データの準備ができていない場合、現在のスレッドは常に read メソッドの呼び出し時にブロックされ、それまで戻りません。データがあり、ノンブロッキング IO の場合、データの準備ができていない場合、read() メソッドは、現在のスレッドに常に待機するのではなく、データの準備ができていないことを通知するフラグ メッセージを返す必要があります。
まず同期 IO と非同期 IO の定義を見てみましょう 『Unix ネットワーク プログラミング』という本における同期 IO と非同期 IO の定義は次のとおりです:
同期 I/O 操作は要求プロセスを引き起こします。その I/O 操作が完了するまでブロックされます。
非同期 I/O 操作では、要求しているプロセスがブロックされません。
文字通りの意味からわかります: 同期 IO は、スレッドが要求した場合に、 IO 操作の場合、IO 操作が完了する前にスレッドがブロックされます。
非同期 IO は、スレッドが IO 操作を要求した場合、その IO 操作によって要求元のスレッドがブロックされないことを意味します。
実際、同期 IO モデルと非同期 IO モデルは、ユーザー スレッドとカーネル間の対話を目的としています。
同期 IO の場合: ユーザーが IO リクエスト操作を発行した後、データの準備ができていない場合は、ユーザー スレッドまたはカーネルによって継続的に処理される データの準備ができているかどうかをポーリングするには、データの準備ができたら、カーネルからユーザー スレッドにデータをコピーします。
非同期 IO: IO リクエスト操作の発行のみが、ユーザースレッド、および IO 操作の 2 段階は、カーネルによって自動的に完了し、スレッド IO 操作が完了したことをユーザーに通知する通知を送信します。つまり、非同期 IO では、ユーザー スレッドがブロックされることはありません。
これが同期 IO と非同期 IO の主な違いです。同期 IO と非同期 IO の主な違いは、データ コピー フェーズがユーザー スレッドによって完了されるか、カーネルによって完了されるかに反映されます。したがって、非同期 IO にはオペレーティング システムによる基礎的なサポートが必要です。
同期IOと非同期IOは、ブロッキングIOやノンブロッキングIOとは異なる概念であることに注意してください。
ブロッキングIOとノンブロッキングIOは、ユーザーがIO操作をリクエストしたときに、データの準備ができていない場合でも、データの準備ができるのを待っていてもユーザースレッドはフラグメッセージを受け取るという事実に反映されています。つまり、ブロッキング IO とノンブロッキング IO は、IO 操作の最初の段階、つまりデータの準備ができているかどうかを確認する際の処理方法に反映されます。
『Unix ネットワーク プログラミング』には、ブロッキング IO、ノンブロッキング IO、多重化 IO、シグナル ドライバー IO、および非同期 IO という 5 つの IO モデルが記載されています。
それでは、これら 5 つの IO モデルの類似点と相違点を紹介しましょう。
1. ブロッキングIOモデル
最も伝統的なIOモデル、つまり、ブロッキングはデータの読み取りと書き込みのプロセス中に発生します。
ユーザースレッドがIOリクエストを発行すると、カーネルはデータの準備ができているかどうかを確認し、そうでない場合はデータの準備ができるまで待機し、ユーザースレッドはブロックされた状態になり、ユーザーはブロックされます。スレッドは CPU を引き渡します。データの準備ができると、カーネルはデータをユーザー スレッドにコピーし、結果をユーザー スレッドに返します。その後、ユーザー スレッドはブロック状態を解放します。
典型的なブロッキング IO モデルの例は次のとおりです。
データの準備ができていない場合、読み取りメソッドで常にブロックされます。
2. ノンブロッキング IO モデル
ユーザースレッドが読み取り操作を開始すると、待つ必要はなく、結果はすぐに取得されます。結果がエラーの場合は、データの準備がまだできていないことが分かるため、読み取り操作を再度送信できます。カーネル内のデータの準備が整い、ユーザー スレッドから再度リクエストを受信すると、すぐにデータをユーザー スレッドにコピーして戻ります。
実際、ノンブロッキング IO モデルでは、ユーザー スレッドはカーネル データの準備ができているかどうかを常に確認する必要があります。これは、ノンブロッキング IO が CPU を引き渡さず、常に CPU を占有することを意味します。
典型的なノンブロッキング IO モデルは一般に次のとおりです:
1 2 3 4 5 6 7 |
data =ソケット.read();
|
データを処理中
🎜🎜
<a href="http://www.php.cn/wiki/130.html" target="_blank">休憩</a>
;🎜🎜
}
🎜🎜 }
🎜🎜🎜🎜🎜しかし、ノンブロッキング IO には非常に深刻な問題があります。while ループ では、カーネル データの準備ができているかどうかを常に確認する必要があるため、CPU 使用率が非常に高くなります。一般に、この方法でループしてデータを読み取ることはほとんどありません。
3.多重化IOモデル
多重化IOモデルは現在よく使われているモデルです。 Java NIO は実際には多重化された IO です。
多重化 IO モデルでは、複数のソケットのステータスを継続的にポーリングするスレッドが存在し、ソケットに実際に読み取りおよび書き込みイベントがある場合にのみ、実際の IO 読み取りおよび書き込み操作が呼び出されます。多重化 IO モデルでは、複数のソケットの管理に使用できるスレッドは 1 つだけであるため、システムは新しいプロセスやスレッドを作成する必要も、これらのスレッドやプロセスを維持する必要もありません。また、実際のソケットの読み取りおよびwrite イベント IO リソースは時間切れになった場合にのみ使用されるため、リソースの使用量が大幅に削減されます。
Java NIO では、selector.select() を使用して、各チャネルの到着イベントがあるかどうかをクエリします。イベントがない場合は、常にそこでブロックされるため、このメソッドはユーザースレッドを発生させます。ブロックされました。
おそらく、マルチスレッド + ブロッキング IO を使用して同様の効果を達成できると言う友人もいるでしょうが、マルチスレッド + ブロッキング IO では各ソケットがスレッドに対応するため、大量のリソース使用量が発生し、特に接続が長い場合、後で多数の接続が発生すると、スレッド リソースが解放されず、パフォーマンスのボトルネックが発生します。
多重化IOモードでは、複数のソケットを1つのスレッドで管理できます。ソケットに実際に読み取りおよび書き込みイベントがある場合にのみ、実際の読み取りおよび書き込み操作のためにリソースが占有されます。したがって、多重化された IO は、接続数が多い状況に適しています。
また、多重化IOがノンブロッキングIOモデルより効率的である理由は、ノンブロッキングIOではソケットの状態がユーザースレッドを通じて継続的に問い合わせられるのに対し、多重化IOでは各ソケットの状態がポーリングされるためです。各ソケットの状態はカーネルによって処理され、この効率はユーザー スレッドの効率よりもはるかに高くなります。
ただし、多重化 IO モデルはポーリングを使用してイベントが到着したかどうかを検出し、到着したイベントに 1 つずつ応答することに注意する必要があります。そのため、多重化IOモデルでは、イベントレスポンスボディが大きくなると、その後のイベントが長時間処理されなくなり、新たなイベントポーリングに影響が出る可能性があります。
4. シグナル駆動型 IO モデル
シグナル駆動型 IO モデルでは、ユーザー スレッドが IO リクエスト操作を開始すると、シグナル 関数 が対応するソケットに登録され、ユーザー スレッドは引き続き処理を実行します。カーネル データの準備ができたら実行する シグナルがユーザー スレッドに送信されます。ユーザー スレッドはシグナルを受信した後、シグナル関数で IO 読み取りおよび書き込み操作を呼び出し、実際の IO 要求操作を実行します。
5. 非同期 IO モデル
非同期 IO モデルは、ユーザー スレッドが読み取り操作を開始すると、すぐに 他の ことを開始できる、最も理想的な IO モデルです。一方、カーネルの観点から見ると、非同期読み取りを受信すると、読み取り要求が正常に開始されたことを示すためにすぐに返されるため、ユーザー スレッドに対してブロックは生成されません。次に、カーネルはデータの準備が完了するのを待ち、データをユーザー スレッドにコピーします。これがすべて完了すると、カーネルはユーザー スレッドに信号を送信して、読み取り操作が完了したことを通知します。言い換えれば、ユーザー スレッドは、IO 操作全体が実際にどのように実行されるかを知る必要はなく、最初にリクエストを開始するだけで、カーネルから返された成功信号を受信できます。これは、IO 操作が完了したことを意味します。データは直接使用できます。
つまり、非同期 IO モデルでは、IO 操作のどちらのフェーズもユーザー スレッドをブロックすることはなく、両方のフェーズがカーネルによって自動的に完了し、操作が完了したことをユーザー スレッドに通知するシグナルが送信されます。完成しました。特定の読み取りおよび書き込みのためにユーザー スレッドで IO 関数を再度呼び出す必要はありません。これはシグナル駆動モデルとは異なります。シグナル駆動モデルでは、ユーザー スレッドがシグナルを受信すると、データの準備ができたことを示し、ユーザー スレッドは実際の読み取りと実行を実行するために IO 関数を呼び出す必要があります。書き込み操作; 非同期 IO モデルでは、シグナルの受信は IO 操作が完了したことを示し、実際の読み取りおよび書き込み操作を実行するためにユーザー スレッドで iO 関数を呼び出す必要はありません。
非同期 IO にはオペレーティング システムの基礎的なサポートが必要であることに注意してください。Java 7 では、非同期 IO が提供されます。
最初の 4 つの IO モデルは実際には同期 IO であり、最後の 1 つだけが真の非同期 IO です。これは、多重化 IO であってもシグナル駆動型モデルであっても、IO 操作の第 2 段階でユーザー スレッドがブロックされるためです。つまり、カーネルによるデータコピーのプロセスはユーザースレッドをブロックします。
従来のネットワーク サービス設計パターンの中には、さらに 2 つの古典的なパターンがあります:
1 つはマルチスレッドで、もう 1 つはスレッド プールです。
マルチスレッドモードの場合、つまり、クライアントが来ると、次の図に示すように、サーバーはクライアントの読み取りおよび書き込みイベントを処理するための新しいスレッドを作成します。このモードは、サーバーのおかげでシンプルで扱いやすく、各クライアント接続はスレッドによって処理され、大量のリソースを消費します。したがって、接続数が上限に達し、別のユーザーが接続を要求すると、リソースのボトルネックが直接発生し、ひどい場合にはサーバーのクラッシュにつながる可能性があります。
したがって、1 スレッド 1 クライアント モデルによって引き起こされる問題を解決するために、スレッド プール方式が提案されています。これは、固定サイズのスレッド プールを作成し、クライアントが来たときにスレッド プールから 1 つを取得することを意味します。スレッド プールはアイドル状態のスレッドを処理に使用します。クライアントは読み取りおよび書き込み操作を完了すると、スレッドの占有を引き継ぎます。したがって、これにより、クライアントごとにスレッドを作成することによるリソースの無駄が回避され、スレッドを再利用できるようになります。
しかし、スレッド プールには欠点もあります。接続のほとんどが長い接続である場合、別のユーザーが接続を要求すると、スレッド プール内のすべてのスレッドが占有される可能性があります。使用可能なアイドル スレッドがない場合、クライアント接続が失敗し、ユーザー エクスペリエンスに影響します。したがって、スレッド プールは、多数の短い接続アプリケーションにより適しています。 したがって、次の 2 つの高性能 IO 設計パターン、Reactor と Proactor が登場しました。 Reactor モードでは、各クライアントはまず対象のイベントを登録し、次にイベントが発生したかどうかを確認するために各クライアントをポーリングするスレッドが存在します。イベントが発生すると、各イベントが順番に処理されます。イベントが処理されると、次の図に示すように、ポーリングを続行するために転送されます:ここから、上記 5 つの IO モデルの多重化 IO は Reactor モードを使用していることがわかります。上の図は、各イベントが順次処理されることを示しています。 もちろん、イベントの処理速度を向上させるために、イベントはマルチスレッドまたはスレッド プールを通じて処理できます。 プロアクターモードでは、イベントが検出されると、新しい非同期操作が開始され、処理のためにカーネルスレッドに渡されます。カーネルスレッドがIO操作を完了すると、操作が完了したことを通知する通知が送信されます。非同期 IO モデルは Proactor モードを使用していることがわかります。
以上がJava NIO: I/O モデルの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。