77

とスレッドプールの違いを誰か説明できepollますか?poll

  • 長所/短所は何ですか?
  • フレームワークに関する提案はありますか?
  • 簡単な/基本的なチュートリアルの提案はありますか?
  • とは Linux 固有epollpollようです... Windows 用の同等の代替手段はありますか?
4

1 に答える 1

220

Threadpool は実際には poll や epoll と同じカテゴリには当てはまらないため、「接続ごとに 1 つのスレッドで多くの接続を処理するためのスレッドプール」のようにスレッドプールを参照していると仮定します。

長所と短所

  • スレッドプール
    • 小規模および中規模の同時実行ではかなり効率的であり、他の手法よりも優れています。
    • 複数のコアを使用します。
    • 一部のシステム (Linux など) では、原則として 100,000 のスレッドを問題なくスケジュールできるにもかかわらず、「数百」を超えて拡張することはできません。
    • 単純な実装では、「雷鳴の群れ」の問題が発生します。
    • コンテキストの切り替えと雷鳴の群れとは別に、メモリを考慮する必要があります。各スレッドにはスタック (通常は少なくとも 1 メガバイト) があります。したがって、1,000 スレッドの場合、スタックだけで 1 GB の RAM が必要になります。そのメモリがコミットされていない場合でも、32 ビット OS ではかなりのアドレス空間が使用されます (実際には 64 ビットでは問題になりません)。
    • スレッド実際に を使用できますepollが、明らかな方法 (すべてのスレッドが をブロックするepoll_wait) は役に立ちません。なぜなら、epoll はそれを待機しているすべてのスレッドを起動するため、それでも同じ問題が発生するからです。
      • 最適な解決策: シングル スレッドが epoll をリッスンし、入力の多重化を行い、完全な要求をスレッドプールに渡します。
      • futexたとえば、スレッドごとの早送りキューと組み合わせて、ここであなたの友達です。ドキュメントが不十分で扱いにくいですが、futex必要なものを正確に提供します。epoll一度に複数のイベントを返す可能性がfutexあり、効率的かつ正確に制御された方法で、一度にN 個のブロックされたスレッドをウェイクアップできます (N がmin(num_cpu, num_events)理想的です)。
      • 実装するのは簡単ではありませんが、注意が必要です。
  • fork(別名オールドファッションスレッドプール)
    • 小規模および中規模の同時実行ではかなり効率的です。
    • 「数百」を超えて拡張することはできません。
    • コンテキスト スイッチは、はるかにコストがかかります (異なるアドレス空間です!)。
    • フォークがはるかに高価な古いシステムでは、スケールが大幅に低下します (すべてのページのディープ コピー)。最新のシステムforkでも「無料」ではありませんが、オーバーヘッドはほとんどコピーオンライトメカニズムによって統合されています。変更も加えられた大規模なデータセットでは、かなりの数のページ フォールトforkが発生し、パフォーマンスに悪影響を及ぼす可能性があります。
    • ただし、30 年以上にわたって確実に機能することが証明されています。
    • 信じられないほど簡単に実装でき、堅固です。いずれかのプロセスがクラッシュしても、世界は終わりません。あなたが間違ってできることは(ほとんど)何もありません。
    • 「雷鳴の群れ」になりやすい。
  • poll/select
    • ほぼ同じものの 2 つのフレーバー (BSD と System V)。
    • やや古くて遅く、ややぎこちない使い方ですが、それらをサポートしないプラットフォームは事実上ありません。
    • 一連の記述子で「何かが起こる」まで待機します
      • 1 つのスレッド/プロセスが一度に多くの要求を処理できるようにします。
      • マルチコアの使用はありません。
    • 待機するたびに、記述子のリストをユーザー空間からカーネル空間にコピーする必要があります。記述子に対して線形検索を実行する必要があります。これにより、その有効性が制限されます。
    • 「数千」にうまくスケーリングしません (実際、ほとんどのシステムで 1024 前後のハード リミット、一部のシステムでは 64 という低い値)。
    • とにかく十数個の記述子しか処理しない場合 (パフォーマンスの問題はありません)、またはより良いものがないプラットフォームをサポートする必要がある場合は、移植可能であるため、これを使用してください。それ以外の場合は使用しないでください。
    • 概念的には、サーバーは分岐したサーバーよりも少し複雑になります。これは、多くの接続と各接続の状態マシンを維持する必要があり、受信した要求を多重化したり、部分的な要求を組み立てたりする必要があるためです。単純な分岐サーバーは 1 つのソケット (リスニング ソケットを数えると 2 つ) を認識し、必要なものが得られるまで、または接続が半分閉じられるまで読み取り、必要なものを書き込みます。ブロッキング、準備状況、飢餓、または無関係なデータが入ってくることを心配する必要はありません。これは他のプロセスの問題です。
  • epoll
    • Linux のみ。
    • 高価な変更と効率的な待機の概念:
      • 記述子が追加されたときに、記述子に関する情報をカーネル空間にコピーします ( epoll_ctl) 。
        • 通常、これはめったに発生しません。
      • イベント待機時にデータをカーネル空間にコピーする必要がないepoll_wait( )
        • 通常、これは非常に頻繁に発生します。
      • 記述子の待機キューにウェイター (またはその epoll 構造体) を追加します。
        • したがって、記述子は、誰がリッスンしているかを認識し、ウェイターが記述子のリストを検索するのではなく、必要に応じてウェイターに直接通知します。
        • poll仕組みの反対の方法
        • O(n) の代わりに、記述子の数に関して小さい k (非常に高速) を持つ O(1)
    • timerfdand で非常にうまく機能しますeventfd(驚くべきタイマーの解像度と精度も)。
    • とうまく連携しsignalfd、ぎこちないシグナル処理をなくし、非常に洗練された方法でシグナルを通常の制御フローの一部にします。
    • epoll インスタンスは、他の epoll インスタンスを再帰的にホストできます
    • このプログラミング モデルによる前提:
      • ほとんどの記述子はほとんどアイドル状態であり、いくつかの記述子で実際に発生することはほとんどありません (「データ受信」、「接続のクローズ」など)。
      • ほとんどの場合、セットから記述子を追加/削除したくありません。
      • ほとんどの場合、何かが起こるのを待っています。
    • いくつかの小さな落とし穴:
      • レベルでトリガーされた epoll は、待機しているすべてのスレッドを起動します (これは「意図したとおりに機能します」)。したがって、スレッドプールで epoll を使用する単純な方法は役に立ちません。少なくとも TCP サーバーの場合、まず部分的なリクエストをアセンブルする必要があるため、大きな問題にはなりません。
      • ファイルの読み取り/書き込みで期待されるように機能しません (「常に準備完了」)。
      • 最近まで AIO で使用できませんでしたが、現在は を介し​​て使用できますeventfdが、(現在まで) 文書化されていない機能が必要です。
      • 上記の仮定が正しくない場合、epoll は非効率的でpollある可能性があり、同等またはそれ以上のパフォーマンスを発揮する可能性があります。
      • epoll「魔法」を行うことはできません。つまり、発生するイベントの数に関しては、依然として必然的に O(N)です。
      • ただし、一度に複数の準備完了通知を返すためepoll、新しい syscall とうまく連携します (最大で指定した数まで、利用可能な数だけ)。これにより、ビジー状態のサーバーで 1 回の syscall でたとえば 15 個の EPOLLIN 通知を受信し、2 回目の syscall で対応する 15 個のメッセージを読み取ることができます (syscall の 93% の削減!)。残念なことに、1 回の呼び出しですべての操作が同じソケットを参照するため、UDP ベースのサービスで最も役に立ちます (TCP の場合、アイテムごとにソケット記述子を受け取る一種の syscall が必要です!)。recvmmsgmaxeventsrecvmmsgrecvmmsmsg
      • 記述子は常に非ブロッキングに設定する必要がEAGAINあり、使用する場合でも確認する必要があります。これは、レポートの準備が整っていて、その後の読み取り (または書き込み)がまだブロックされるというepoll例外的な状況があるためです。これは、一部のカーネルの/にも当てはまります (ただし、おそらく修正されています)。epollpollselect
      • 素朴な実装では、遅い送信者の飢餓が発生する可能性があります。通知の受信時に が返されるまでやみくもに読み取る場合EAGAIN、低速の送信者を完全に枯渇させながら、高速の送信者からの新しい受信データを無期限に読み取ることができます (データが十分に速く受信し続ける限り、EAGAINかなりの時間表示されない可能性があります! )。poll/にも同様に適用されselectます。
      • ドキュメント (man ページと TLPI の両方) が曖昧 (「おそらく」、「すべき」、「可能性がある」) であり、その操作について誤解を招く場合があるため、エッジ トリガー モードには、いくつかの状況でいくつかの癖と予期しない動作があります。
        ドキュメントには、1 つの epoll を待機している複数のスレッドがすべて通知されると記載されています。epoll_waitさらに、前回の呼び出し以降 (または前回の呼び出しがなかった場合は記述子が開かれた以降) に IO アクティビティが発生したかどうかを通知することを示しています。
        エッジ トリガー モードでの真の観察可能な動作は、「 を呼び出した最初スレッドを起動しepoll_wait誰かが最後にまたは epoll_wait 記述子の読み取り/書き込み関数、その後は、誰かが記述子の読み取り (または書き込み) 関数を呼び出した後に発生する操作について、次のスレッドを呼び出しているか、既にブロックされているスレッドに 再び準備ができていることを報告するだけです。 、あまりにも...ドキュメントが示唆しているものとはまったく異なります。epoll_wait
  • kqueue
    • BSDと の類似epoll性、異なる使用法、同様の効果。
    • Mac OS X でも動作します
    • より高速であると噂されています (私は使用したことがないので、それが本当かどうかはわかりません)。
    • イベントを登録し、単一の syscall で結果セットを返します。
  • IO 完了ポート
    • Windows 用の Epoll、または強化版の epoll。
    • 何らかの方法で待機可能または警告可能なすべてのもの (ソケット、待機可能タイマー、ファイル操作、スレッド、プロセス)とシームレスに連携します。
    • Microsoft が Windows で 1 つの問題を解決したとすれば、それは完了ポートです。
      • スレッドの数に関係なく、箱から出してすぐに安心して動作します
      • 雷鳴の群れなし
      • LIFO の順序でスレッドを 1 つずつ起こします
      • キャッシュをウォームに保ち、コンテキストの切り替えを最小限に抑えます
      • マシン上のプロセッサの数を尊重するか、必要な数のワーカーを提供します
    • アプリケーションがイベントを投稿できるようにします。これにより、非常に簡単でフェイルセーフで効率的な並列作業キューの実装に役立ちます (私のシステムでは 1 秒あたり 500,000 タスク以上をスケジュールします)。
    • マイナーな欠点: 一度追加したファイル記述子を簡単に削除できません (閉じてから再度開く必要があります)。

フレームワーク

libevent -- 2.0 バージョンは、Windows での完了ポートもサポートしています。

ASIO -- プロジェクトで Boost を使用している場合は、もう探す必要はありません。これはすでに boost-asio として利用できます。

簡単な/基本的なチュートリアルの提案はありますか?

上記のフレームワークには、詳細なドキュメントが付属しています。Linux のドキュメントと MSDN では、epoll と補完ポートについて詳しく説明しています。

epoll を使用するためのミニチュートリアル:

int my_epoll = epoll_create(0);  // argument is ignored nowadays

epoll_event e;
e.fd = some_socket_fd; // this can in fact be anything you like

epoll_ctl(my_epoll, EPOLL_CTL_ADD, some_socket_fd, &e);

...
epoll_event evt[10]; // or whatever number
for(...)
    if((num = epoll_wait(my_epoll, evt, 10, -1)) > 0)
        do_something();

IO 完了ポートのミニチュートリアル (異なるパラメーターで CreateIoCompletionPort を 2 回呼び出すことに注意してください):

HANDLE iocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0, 0); // equals epoll_create
CreateIoCompletionPort(mySocketHandle, iocp, 0, 0); // equals epoll_ctl(EPOLL_CTL_ADD)

OVERLAPPED o;
for(...)
    if(GetQueuedCompletionStatus(iocp, &number_bytes, &key, &o, INFINITE)) // equals epoll_wait()
        do_something();

(これらのミニチュートリアルでは、あらゆる種類のエラー チェックが省略されています。タイプミスがないことを願っていますが、ほとんどの場合、いくつかのアイデアを得るには問題ありません。)

編集:
完了ポート (Windows) は、概念的には epoll (または kqueue) とは逆に機能することに注意してください。それらは、その名前が示すように、準備完了ではなく、完了を示します。つまり、非同期リクエストを開始し、しばらくして完了したことを知らされるまでそれを忘れます (成功したか、それほど成功しなかったかのどちらかであり、「すぐに完了した」という例外的なケースもあります)。
epoll を使用すると、「一部のデータ」 (場合によっては 1 バイト程度) が到着して使用可能になるか、ブロックせずに書き込み操作を実行できる十分なバッファー スペースがあることが通知されるまでブロックします。そうして初めて、実際の操作を開始し、ブロックされないことが期待されます (予想される以外は、厳密な保証はありません。したがって、記述子を非ブロックに設定し、EAGAIN [EAGAINおよびEWOULDBLOCK ] を確認することをお勧めします。ソケットの場合、うれしいことに、標準では 2 つの異なるエラー値が許容されます])。

于 2011-03-27T14:25:23.037 に答える