従来のプログラミングモジュールでは、I/O操作は通常のローカル関数呼び出しのようなものです。このプログラムは、機能が実行される前にブロックされ、実行を継続できません。ブロックされたI/Oは、各プロセスが独立した人のようであり、すべての人を区別する目的で、通常は誰もが同時に1つのことしかできず、次に何をするかを決定する前に前のことを待つ必要があります。ただし、コンピューターネットワークとインターネットで広く使用されている「1つのユーザー、1つのプロセス」のこのモデルは非常にスケーラブルです。複数のプロセスを管理する場合、多くのメモリを消費し、コンテキストの切り替えも多くのリソースを占有します。これらはオペレーティングシステムの大きな負担であり、プロセスの数が増えると、システムのパフォーマンスが急激に崩壊します。
マルチスレッドは代替品です。スレッドは、同じプロセスで他のスレッドとメモリを共有する軽量プロセスです。これは、複数のスレッドを同時に実行するために使用される従来のモデルの拡張に似ています。 1つのスレッドがI/O操作を待っている場合、他のスレッドがCPUを引き継ぐことができます。 I/O操作が完了すると、前で待機しているスレッドが目覚めます。つまり、実行中のスレッドを中断してから後で再開できます。さらに、スレッドは、一部のシステムでは、マルチコアCPUの異なるコアで並行して実行できます。
プログラマーは、スレッドが何時に実行されるのかわかりません。共有メモリへの同時アクセスを処理するように注意する必要があるため、ロックやセマフォを使用するなどの特定のデータ構造へのアクセスを同期させるには、特定の動作と計画でスレッドを実行させるために、いくつかの同期プリミティブを使用する必要があります。スレッド間で共有状態に大きく依存しているアプリケーションは、強いランダム性と発見の困難に簡単に奇妙な問題を抱えています。
別の方法は、マルチスレッドコラボレーションを使用することです。ここでは、CPUを明示的にリリースし、CPU時間を他のスレッドに引き渡す責任があります。スレッドの実行計画を個人的に制御するため、同期の必要性は減少しますが、プログラムの複雑さとエラーの可能性も高まり、マルチスレッドの問題を回避しません。
イベント駆動型プログラミングとは何ですか
イベント駆動型プログラミングはプログラミングスタイルであり、イベントがプログラムの実行プロセスを決定します。イベントは、イベントハンドラーまたはイベントコールバックによって処理されます。イベントコールバックは、データベースがクエリの結果を返したり、ユーザーがボタンをクリックしたりするなど、特定のイベントが発生したときに呼び出される関数です。
従来のブロックされたI/Oプログラミングモードでは、データベースクエリが次のようになる可能性があることを思い出してください。
コードコピーは次のとおりです。
result = query( 'select * from posts from id = 1');
do_something_with(result);
上記のクエリ関数は、基礎となるデータベースがクエリ操作を完了して返すまで、現在のスレッドまたはプロセスを待機状態に保ちます。
イベント駆動型モデルでは、このクエリは次のようになります。
コードコピーは次のとおりです。
query_finished = function(result){
do_something_with(result);
}
query( 'select * from id = 1'、query_finished);
最初に、クエリが完了した後に何をすべきかを含むquery_finishedと呼ばれる関数を定義します。次に、この関数をパラメーターとしてクエリ関数に渡します。 Query_Finishedは、クエリの結果を返すだけでなく、クエリ実行後に呼び出されます。
興味のあるイベントが発生した場合、定義する関数は、結果値を単に返す代わりに呼び出されます。このプログラミングモデルは、イベント駆動型プログラミングまたは非同期プログラミングと呼ばれます。これは、ノードの最も明白な機能の1つです。このプログラミングモデルは、I/O操作を実行するときに現在のプロセスがブロックされないことを意味します。したがって、複数のI/O操作を並行して実行でき、操作が完了した後に対応するコールバック関数が呼び出されます。
イベント駆動型プログラミングの基礎となる層は、イベントループに依存しています。イベントループは、基本的に、イベント検出とイベントプロセッサがこれら2つの機能の連続ループコールをトリガーする構造です。各ループでは、イベントループメカニズムが発生したイベントを検出する必要があります。イベントが発生すると、対応するコールバック関数を見つけて呼び出します。
イベントループは、プロセスで実行されているスレッドにすぎません。イベントが発生すると、イベントプロセッサは単独で実行され、中断されません。つまり、次のとおりです。
1.せいぜい1つのイベントコールバック関数は特定の瞬間に実行されています
2.実行時にイベントプロセッサが中断されていません
これにより、開発者は、スレッドの同期と共有メモリの同時変更について頭痛を感じることができなくなりました。
よく知られている秘密:
昔、システムプログラミングコミュニティの人々は、イベント主導のプログラミングが多くのコンテキストを節約する必要がないため、高い並行性サービスを作成するための最良の方法であることを知っていたため、コンテキストの切り替えではなく、多くのメモリを節約し、多くの実行時間を節約しました。
ゆっくりと、この概念は他のプラットフォームやコミュニティに浸透し、Rubyのイベントマシン、PerlのAnyevnet、Pythonのねじれなど、有名なイベントループの実装が登場しました。これらに加えて、他にも多くの実装や言語があります。
これらのフレームワークを開発するには、フレームワークとフレームワーク固有のクラスライブラリに関連する特定の知識を学ぶ必要があります。たとえば、イベントマシンを使用する場合、非ブロッキングの利点を享受するために、同期クラスライブラリの使用を避け、イベントマシンの非同期クラスライブラリのみを使用できます。ブロッキングライブラリ(ほとんどのRubyの標準ライブラリなど)を使用すると、イベントループが常にブロックされ、I/Oイベントの処理が随時ブロックされるため、サーバーは最適なスケーラビリティを失います。
ノードはもともと非ブロッキングI/Oサーバープラットフォームとして設計されていたため、一般的に、実行しているすべてのコードが非ブロッキングであると予想されるはずです。 JavaScriptは非常に小さく、I/Oモデルを強制しないため(標準のI/Oクラスライブラリがないため)、ノードは非常に純粋な環境に組み込まれており、レガシーの問題はありません。
ノードとJavaScriptが非同期アプリケーションを単純化する方法
Nodeの著者Ryan Dahlは最初にCを使用してこのプロジェクトを開発しましたが、関数呼び出しを維持するコンテキストは複雑すぎて、コードの複雑さが高くなることがわかりました。その後、彼はルアに切り替えましたが、ルアはすでにいくつかのブロッキングI/Oライブラリを持っています。ブロッキングとノンブロッキングの混合により、開発者を混乱させると、多くの人々がスケーラブルなアプリケーションを構築することができなくなります。したがって、ルアもダールに見捨てられました。最後に、彼はJavaScript、JavaScriptの閉鎖、および第1レベルのオブジェクトの機能に目を向け、JavaScriptをイベント駆動型プログラミングに非常に適しています。 JavaScriptの魔法は、ノードが非常に人気がある主な理由の1つです。
閉鎖とは何ですか
閉鎖は特別な機能として理解できますが、定義されているスコープ内の変数を継承してアクセスできます。コールバック関数を別の関数にパラメーターとして渡すと、後で呼び出されます。魔法は、このコールバック関数が後で呼び出されると、実際にそれがそれ自体を定義するコンテキストと親のコンテキストの変数を覚えており、それらにも通常アクセスできることです。この強力な機能は、ノードの成功の核心です。
次の例では、JavaScriptクロージャーがWebブラウザーでどのように機能するかを示します。ボタンでスタンドアロンのイベントを聞きたい場合は、これを行うことができます。
コードコピーは次のとおりです。
var clickCount = 0;
document.getElementById( 'mybutton')。onclick = function(){
ClickCount += 1;
Alert( "Clicked" + ClickCount + "Times");
};
これは、jQueryを使用する場合です。
コードコピーは次のとおりです。
var clickCount = 0;
$( 'ボタン#mybutton')。クリック(function(){
ClickedCount ++;
alert( 'clicked' + clickcount + 'times');
});
JavaScriptでは、関数は最初のタイプのオブジェクトです。つまり、他の関数へのパラメーターとして関数を渡すことができます。上記の2つの例では、前者は関数を別の関数に割り当て、後者はパラメーターとして関数を別の関数に渡します。クリックイベント処理関数(コールバック関数)は、関数が定義するコードブロックの下の各変数にアクセスできます。この例では、親の閉鎖で定義されたClickCount変数にアクセスできます。
ClickCount変数はグローバルスコープ(JavaScriptの最も外側のスコープ)にあり、ユーザーがボタンをクリックする回数を節約します。通常、変数をグローバルスコープの下に保存するのは悪い習慣です。なぜなら、他のコードと競合するのは簡単であり、それらを使用する場所に変数をローカルスコープに配置する必要があるからです。ほとんどの場合、1つの関数でコードを包むだけで、別の閉鎖を作成することと同等であり、このようにグローバル環境の汚染を簡単に避けることができます。
コードコピーは次のとおりです。
(関数() {
var clickCount = 0;
$( 'ボタン#mybutton')。クリック(function(){
ClickCount ++;
alert( 'clicked' + clickcount + 'times');
});
}());
注:上記のコードの7行目は関数を定義し、すぐに呼び出します。これは、JavaScriptの一般的な設計パターンです。関数を作成して新しいスコープを作成します。
閉鎖が非同期プログラミングにどのように役立つか
イベント駆動型のプログラミングモデルでは、最初にイベントが発生した後に実行するコードを書き込み、次に関数にコードを配置し、最終的にパラメーターとしてパラメーターとして渡して、後で発信者関数によって呼び出します。
JavaScriptでは、関数は孤立した定義ではありません。また、宣言されている範囲のコンテキストを覚えています。このメカニズムにより、JavaScript関数は、関数定義が配置されているコンテキストと、親コンテキストのすべての変数にアクセスできます。
コールバック関数を発信者にパラメーターとして渡すと、関数は後のポイントで呼び出されます。コールバック関数を定義するスコープが終了したとしても、コールバック関数が呼び出されると、終了スコープとその親スコープのすべての変数にアクセスできます。最後の例と同様に、コールバック関数はjQueryのクリック()内に呼び出されますが、それでもクリックカウント変数にアクセスできます。
閉鎖の魔法は以前に示されています。状態変数を関数に渡すことで、状態を維持せずにイベント駆動型プログラミングを実行できます。 JavaScriptの閉鎖メカニズムは、それらを維持するのに役立ちます。
まとめ
イベント駆動型プログラミングは、イベントトリガーを通じてプログラム実行プロセスを決定するプログラミングモデルです。プログラマーは、興味のあるイベント(通常はイベントハンドラーと呼ばれる)のコールバック関数を登録し、イベントが発生したときに登録されたイベントハンドラーを呼び出します。このプログラミングモデルには、従来のブロッキングプログラミングモデルにはない多くの利点があります。過去には、同様の機能を実装するには、マルチプロセス/マルチスレッドを使用する必要があります。
JavaScriptは、最初のタイプのオブジェクトの関数と閉鎖特性のために強力な言語であり、イベント駆動型プログラミングに非常に適しています。