序文
この記事では、多くのユーザーに同時に役立つWebアプリケーションのプログラミングの重要な側面に触れたいと思います。つまり、すべての退屈な非同期I / O、多重化などをすでに分析します。
次の目標が追求されています。
- この分野で資料を体系化するために、用語のいくつかの矛盾を議論する
- 多くの顧客サービスアプリケーションを構築する基盤を完全に分解します。
- 多くのクライアントに役立つ将来のPythonアプリケーションの戦略を開発する
- 頭の中に鮮明な画像を作成します(理由がない限り、彼らがあなたが理解していると言うわけではありません-説明できるとき)
なんで?
PHPプログラミングの長年は報われました-私は舞台裏で何が起こっているのか疑問に思いませんでした。 しかし、プロジェクトがゆっくりと遅くなり始めましたが、確かに遅くなりました-私はそれを心に取る時間であると判断し、Pythonを勉強し始めました(私は今成功しています)。 しかし、「fork」、「socket」、「eventloop」、「multiprocessing」、「epoll」など、あらゆる種類の単語に間違いなく飽き飽きしていました。 もっと深く掘り下げることにしました。 それから出てきたものはあなた次第です。 それにもかかわらず、特定の混乱が存在します。 この確認は、参考文献[
6、7 ]で確認できます。
int main()
この記事では、Linux 2.6以降に基づいたLinux I / Oについて説明します。 素材は主に私のような人に焦点を合わせているため(水を加熱する装置にはオプションで音響表示器が装備されています)、多くの基本事項を確認する必要があるため、必要のない項目はスキップしてください。
基本的な概念
Linuxでは、
ファイルは基本的な抽象概念です。 Linuxは「すべてがファイルである」という哲学を順守しています。つまり、ほとんどのやり取りはファイルの読み取りと書き込みによって実現されます。 ファイル操作は、一意の記述子(ファイル記述子またはfd)を使用して実行されます。 Linuxシステムのプログラミングのほとんどには、ファイル記述子の操作が含まれます。
通常のファイル (通常のファイル)があります-これは私たちが慣れているもの(通常の意味で最も普通の「ファイル」)であり、
特殊ファイルはファイルとして表されるオブジェクトです。 Linuxは4種類の特殊ファイルをサポートしています。
- ブロックデバイスファイル
- キャラクターI / Oデバイスファイル
- 名前付きパイプ(名前付きパイプまたはFIFO)
- ソケット
ソケットは、異なるコンピューター(クライアントサーバー)に配置できる2つの異なるプロセス間の通信を提供するため、後者がまさに私たちの関心の範囲です。 実際、ネットワークプログラミングとインターネットのプログラミングはソケット上に構築されています。
最初の近似では、通常のファイルとソケットのみを考慮するだけで十分です。
入力/出力モデル
合計すると、Unixライクシステムでは、5 + 1の異なるI / Oモデルが利用可能です。 「プラスワン」は少し後で説明しますが、今のところ、各モデルをより詳細に検討してください。
ブロッキングI / O(ブロッキングI / O)
デフォルトでは、すべての入力はブロックスタイルで出力されます。 入力/出力のブロックで発生するプロセスの概略図を検討してください。

この場合、プロセスは
recvfromシステムコールを行います。 その結果、データが到着し、システムコールがそれをアプリケーションバッファに書き込むまで、プロセスはブロックされます(スリープ状態になります)。
その後、システムコールは終了し(OKを返す)、データを処理できます。
明らかに、このアプローチには非常に大きな欠点があります-データを待機している間(および接続の品質などのために非常に長い時間がかかる場合があります)、プロセスはスリープし、リクエストに応答しません。
ノンブロッキングI / O(ノンブロッキングI / O)
ソケットを操作するときに非ブロックモードを設定し、実際に次のことをカーネルに伝えます:「プロセスをロックに浸す(スリープ)せずに、実行したい入力/出力が不可能な場合、ブロックせずにこれを行うことができないというエラーを返します」ノンブロッキング入出力で発生するプロセスの概略図。

readにシステムコールを送信する最初の3回は、結果を返しません。 カーネルはデータがないことを認識し、EWOULDBLOCKエラーを返します。
最後のシステムコールは成功します データを読み取る準備ができました。 その結果、カーネルはプロセスバッファにデータを書き込み、処理に使用できるようになります。
これに基づいて、非ブロックモードで開いているソケットに対して常にrecvfrom(データを要求)を呼び出すループを作成できます。 このモードはポーリングと呼ばれます。 アプリケーションは、データの可用性についてシステムのコアを常にポーリングします。 基本的に、いくつかのソケットを順番にポーリングして、データがある最初のソケットから読み取るための制限はありません。 このアプローチは、大きなオーバーヘッド(プロセッサのオーバーヘッド)につながります。
I / Oの多重化(I / Oの多重化)
一般的に、多重化という言葉は「コンパクト」と解釈されます。 時間管理のモットー「それ以上のことを学ぶ」でうまく説明できると思います。 I / Oを多重化する場合、OSで使用可能なシステムコールの1つ(select、poll、pselect、dev / poll、epoll(Linuxに推奨)、kqueue(BSD)などのマルチプレクサー)をオンにし、ブロックする代わりにブロックします実際のI / O呼び出し。 概略的には、多重化プロセスが画像に示されています。

ソケットが読み取り可能になるのを待つselect'aを呼び出すと、アプリケーションがブロックされます。 その後、カーネルは
読み取り可能なステータスを返し、
recvfromを使用してデータを取得できます。 一見-完全な失望。 同じロック、待機、さらに2つのシステムコール(selectおよびrecvfrom)-高いオーバーヘッド。 ただし、ブロッキング方式とは異なり、select(およびその他のマルチプレクサ)を使用すると、1つではなく複数のファイル記述子からのデータを期待できます。 これは、特にリソースが非常に限られている場合、多くの顧客にサービスを提供する最も合理的な方法であると言わなければなりません。 これはなぜですか? マルチプレクサがダウンタイム(スリープ)を減らすためです。 私は次の画像を説明しようとします

ソケットに対応する記述子のプールが作成されます。 接続中にEINPROGRESS応答が届いた場合でも、これは接続が確立されていることを意味します。 テスト中のマルチプレクサは、最初に解放されたものを引き続き使用します。
今注目! 最も重要なこと!
質問に答えてください:どのイベントがより可能性が高いですか イベントAの場合、データは
特定のソケットまたはイベントBの準備ができていること、データは少なくとも
1つのソケットの準備ができていること
? 。 回答:B
多重化の場合、すべてのソケットがループでチェックされ、最初のソケットが準備されます。 彼と仕事をしている間、他の人も間に合うように到着することができます。つまり、アイドル時間の時間を短縮します(最初に長い時間待つことができますが、残りの時間はずっと短くなります)。
(ブロッキングを使用して)通常の方法で問題を解決する場合、どの接続から最初の1秒、2秒を読み取るかなどを推測する必要があります。 つまり 私たちは100%間違えて待っています、そしてこの時間を無駄にすることはできませんでしたが
スレッド/子プロセスのI / O(スレッドまたはプロセスごとに1つのファイル記述)
最初に5 + 1の方法があると言いますが、これは、それぞれブロッキングI / Oが実行される複数のストリームまたはプロセスが使用される場合のアプローチでした。 I / O多重化に似ていますが、いくつかの欠点があります。 Linuxのスレッドは(いわゆるシステムコマンドを使用すると)非常に高価なので、スレッドを使用するとオーバーヘッドが増加します。 さらに、Pythonをプログラミング言語と見なす場合、PythonにはGILが含まれており、したがって、各瞬間に1つのプロセス内で実行できるスレッドは1つだけです。 別のオプションは、ブロッキングスタイルでI / Oを処理する子プロセスを作成することです。 ただし、プロセス間の相互作用(IPC-プロセス間通信)について考える必要があり、これにはいくつかの困難が伴います。 さらに、コアの総数が1を超えない場合、このアプローチには疑わしい利益があります。 ところで、私が知る限り、Apacheはこのように動作し(MPMプリフォークまたはスレッド)、スレッドまたは別のプロセスでクライアントにサービスを提供します。
入力信号駆動出力(信号駆動I / O)
シグナルを使用して、ブロックせずにデータを読み取ることが可能になったときに(ハンドルを読み取る準備ができた)カーネルに
SIGIO形式のシグナルを送信させることができます。 概略的に、このアプローチは画像に示されています。

まず、シグナルを操作するためのソケットパラメーターを設定し、sigactionシステムコールを使用してシグナルハンドラーを割り当てる必要があります。 結果は即座に返されるため、アプリケーションはブロックされません。 実際、コアがすべての作業を処理します。 データの準備ができたらモニターし、SIGIOシグナルを送信します。SIGIOシグナルは、それにインストールされたハンドラー(コールバック関数、コールバック)を呼び出します。 したがって、recvfrom呼び出し自体は、シグナルハンドラーまたはメインプログラムストリームで行うことができます。 私が知る限り、ここには1つの問題があります。このタイプのプロセスごとにシグナルは1つしかありません。 つまり 一度に1つのfdしか使用できません(確かではありませんが)
非同期I / O(非同期I / O)
非同期の入出力は、特別なシステムコールを使用して実行されます。 これは単純なアイデアに基づいています-カーネルには、操作を開始し、I / O操作が完全に完了したときに(シグナルを使用するなどして)通知するコマンドが与えられます(データのプロセスバッファーへのコピーを含む)。 これは、この実装と信号の実装の主な違いです。 概略的には、非同期入力出力のプロセスが画像に示されています。

システムコールaio_readを作成し、必要なすべてのパラメーターを指定します。 残りの作業は、コアによって行われます。 もちろん、I / Oが完了したことをプロセスに通知するメカニズムが必要です。 そして、ここで多くの問題が潜在的に発生します。 しかし、その別の時間についての詳細。
一般的に、この用語には多くの問題があります。リンクの例はすでに与えられています。 多くの場合、非同期、非ブロッキング、および多重化された入力出力の間には概念の混乱があります。「非同期」の概念そのものがさまざまな方法で解釈できるためです。 私の理解では、非同期とは時間的に独立していることを意味します。 つまり、打ち上げられた後、彼はそれが満たされるまで彼の人生を生き、そして私たちはただ結果を得る
実際に
実際には、タスクに基づいた異なるモデルの組み合わせ。 次のようにします。
- ロック付きの各操作にスレッド/プロセスを使用します-有益ではありません
- 異なるスレッド/プロセスの多くのクライアント。 各スレッド/プロセスはマルチプレクサを使用します
- 異なるスレッド/プロセスの多くのクライアント。 使用される非同期入力/出力(aio)
- サーバーをカーネルに埋め込むだけです
C10K問題の詳細
まとめ
混乱しやすいものの違いについて、少なくとも少しは明らかになることを願っています。
まあ、はい、多重化ルール(aioが終了するまで)。
参考文献を調べてみると、ソケットとは異なり、通常のファイルを非ブロッキングモードに転送できないという結論に達しました。 Aioは彼らのために利用可能であるようです、それはここで議論されます:
Linux上の非同期I / Oまたは地獄へようこそ文献と参考文献
- ロバートラブ「Linux。 システムプログラミング
- W.リチャードスティーブンス、ビルフェナー、アンドリューM.ラドフ「Unix Network Programming Volume 1、第3版:ソケットネットワーキングAPI」
- Stevens R.、Rago S. Unix。 プロフェッショナルプログラミング、第2版
- みんなのお気に入りC10K問題
- Linuxでの非同期I / Oまたは地獄へようこそ
- 2つの高性能I / O設計パターンの比較
- 非同期と非ブロッキング
- ブロッキングと ノンブロッキングソケット