none
非同期Socketの色々な実現方法について RRS feed

  • 質問

  • Socketは難しく、その中で非同期Socketについてわからないことがあります。
    ブロッキングについては理解しています。
    スケーラブルなサーバーを実現するため非同期Socketを使用したいと考えます。
    実現には、

    1. Socket.Begin系メソッドを使う
    2. AcceptSocketでクライアント毎のソケットをキューに入れてスレッドプールで取り出しながらブロッキングメソッドで実行させる。

    この2つの方法に本質的な違いはあるでしょうか?

    IO完了ポートというものがパフォーマンスが高いと聞きます。これはどういった理由で優れているのでしょうか?
    これにはCreateIoCompletionPort というWin32 APIを使うらしいですが.NET Frameworkで同等のことは行うことは可能でしょうか?

    2012年4月18日 21:26

回答

  • Socket.Begin系の非同期メソッドを使用した場合、.NET内部で非同期の実現方法としてIO完了ポートを使用します。

    なぜ、IO完了ポートだとパフォーマンスが高いのかですが、

    • 同期IOの場合、メソッド呼び出しは最終的にOSカーネル呼び出しとなりますが、その際、同期ですので呼び出したスレッドはカーネルからの応答を待つために停止します。1スレッドで同期呼び出しをするなら1度に1ソケットしか扱えませんからパフォーマンスが悪いことがわかります。1ソケット=1スレッドとすれば同時にソケットを扱えるようになりますが、スレッド辺りのスタックメモリーが1MB程度ですので、例えば1000ソケット=1000スレッドを使いOSを呼び出すとそれだけでメモリーが1GB消費されていることになります。
    • 非同期IOの場合、メソッド呼び出しの後、OSカーネル呼び出しはすぐに返ります。ですので、同期IOと異なりスレッド数は多く必要としません。完了したらイベント(EventWaitHandleだったかな?)を投げます。アプリケーション側としてはイベントを確認するまで完了したかを確認できませんので、パフォーマンスはよくても応答性能が悪くなります。
    • 非同期IOで更にIO完了ポートを組み合わせた場合、メソッド呼び出しの後、OSカーネル呼び出しはすぐに返ります。ですので、同期IOと異なりスレッド数は多く必要としません。完了したらIO完了ポート経由でスレッドプールのスレッドを使ってコールバックが呼び出されます。アプリケーションとしてはイベントを確認することなくコールバックを実行できるのでパフォーマンスが最もよくなります。

    # というのが私の理解です。突っ込み歓迎^^;

    • 回答としてマーク 和和和 2012年4月19日 21:34
    2012年4月19日 1:05

すべての返信

  • Socket.Begin系の非同期メソッドを使用した場合、.NET内部で非同期の実現方法としてIO完了ポートを使用します。

    なぜ、IO完了ポートだとパフォーマンスが高いのかですが、

    • 同期IOの場合、メソッド呼び出しは最終的にOSカーネル呼び出しとなりますが、その際、同期ですので呼び出したスレッドはカーネルからの応答を待つために停止します。1スレッドで同期呼び出しをするなら1度に1ソケットしか扱えませんからパフォーマンスが悪いことがわかります。1ソケット=1スレッドとすれば同時にソケットを扱えるようになりますが、スレッド辺りのスタックメモリーが1MB程度ですので、例えば1000ソケット=1000スレッドを使いOSを呼び出すとそれだけでメモリーが1GB消費されていることになります。
    • 非同期IOの場合、メソッド呼び出しの後、OSカーネル呼び出しはすぐに返ります。ですので、同期IOと異なりスレッド数は多く必要としません。完了したらイベント(EventWaitHandleだったかな?)を投げます。アプリケーション側としてはイベントを確認するまで完了したかを確認できませんので、パフォーマンスはよくても応答性能が悪くなります。
    • 非同期IOで更にIO完了ポートを組み合わせた場合、メソッド呼び出しの後、OSカーネル呼び出しはすぐに返ります。ですので、同期IOと異なりスレッド数は多く必要としません。完了したらIO完了ポート経由でスレッドプールのスレッドを使ってコールバックが呼び出されます。アプリケーションとしてはイベントを確認することなくコールバックを実行できるのでパフォーマンスが最もよくなります。

    # というのが私の理解です。突っ込み歓迎^^;

    • 回答としてマーク 和和和 2012年4月19日 21:34
    2012年4月19日 1:05
  • 回答というよりは私の解釈の説明になるので、違っているかもしれません。参考程度にとどめてください。

    ・同期の場合:何かを受け取るまで制御が戻らないので他の処理を行うことができなくなる。しかし、それはある種の最高パフォーマンスを得られる。
    ・非同期の場合:本流の処理を行うことができる。コールバック関数が呼び出されるのを待つだけになるが、コールされるタイミングは他人任せになる。もしくは、ちょくちょく自分で確認しなければならない。そのためワーカーとしての処理効率は、ほどほどである。
    ・IO完了ポートの場合:ワーカースレッドが処理を完了するまで受け持ち続けるので、ワーカーそのもののパフォーマンスは最高になる。しかし、必要なリソース(スレッド、メモリ)は一番多い。

    あと、自分では使ったことないのですがIHostIoCompletionManagerというのがありました。

    2012年4月19日 12:58
  • CreateIoCompletionPortを使うときき、難しいなと感じていました。

    が、Begin系が内部でそれが使われているということですのでパフォーマンス的には期待できそうです。
    確かに明示的にBeginメソッドが用意されているからにはメリットがあったのでしょう。
    一応両コード書いてみて負荷を与えて調べてみたいと思います。

    2012年4月19日 21:34
  • 質問も解決しているようなので、回答というより佐祐理さんへの突っ込みになってしまいますが・・・。

    • 非同期IOで更にIO完了ポートを組み合わせた場合、メソッド呼び出しの後、OSカーネル呼び出しはすぐに返ります。ですので、同期IOと異なりスレッド数は多く必要としません。完了したらIO完了ポート経由でスレッドプールのスレッドを使ってコールバックが呼び出されます。アプリケーションとしてはイベントを確認することなくコールバックを実行できるのでパフォーマンスが最もよくなります。

    1.完了イベントの処理にスレッドプールのスレッドを使うのなら、同期IOよりスレッド数が多く必要としないとは言えない気もしてしまいます・・・。

    2.「アプリケーションのメインスレッドが処理中でも、別スレッドがイベント処理するので応答性を良くする事ができる」とする方が良くないでしょうか?


    というのが突っ込みで、質問の回答は

    1. Socket.Begin系メソッドを使う
    2. AcceptSocketでクライアント毎のソケットをキューに入れてスレッドプールで取り出しながらブロッキングメソッドで実行させる。

    この2つの方法が本質的には別内容ですね・・・。

    ブロッキングメソッド=同期I/O ですから、

    1. は非同期I/Oを使って応答処理を記述する。

    2. はクライアント毎に別スレッドで同期I/Oを使って応答処理を記述する。

    という事になってますよね。


    実質的にはどちらも似たような処理をされることになると思いますが、記述次第でまったくの別物になりと思いますよ。

    2012年4月20日 17:02