C++コパスとネットワークプログラミング

5189 ワード

C++コパスとネットワークプログラミング
きょうてい
一連の互いに依存するスレッド間でCPUが順次使用され,毎回1つのスレッドのみが動作し,他のスレッドはスリープ状態にあると考えられるコラボレーションプログラムである.コンシステントは、実行中のポイントで実行を一時停止し、実行を再開するときに一時停止したポイントから実行を続行します.コラボレーションはpython,lua,rubyなどのスクリプト言語で広く採用されているだけでなく,golang rust−langなどの次世代のマルチコア向けプログラミング言語で並列の基本単位として採用されている非常に有用なプログラムコンポーネントであることが実証されている.コプロセッサは、従来のスレッドと比較して2つの主要な利点を有するユーザ空間スレッドと考えられる.
  • は、スレッドとは異なり、任意の時点でシステムスケジューリングによって中断される可能性があるのではなく、CPUを自発的に譲渡し、所望の次のスレッド実行を提供する.したがって,コンポジットの使用はより明確で分かりやすく,ロック機構を必要としない場合が多い.
  • は、スレッドと比較して、コプロセッサの切り替えはプログラムによって制御され、カーネル空間ではなくユーザ空間で発生するため、切り替えのコストは非常に小さい.

  • ネットワークプログラミングモデル
    まず、よく使われるネットワークプログラミングモデルを簡単に振り返ってみましょう.ネットワークプログラミングモデルは、同期モデルと非同期モデルの2つに大きく分けることができる.
  • 同期モデル:
  • 同期モデルはブロックIOモードを使用し、ブロックIOモードでreadなどのIO関数を呼び出すと、IOが完了または失敗するまでスレッドがブロックされます.
    同期モデルの典型的な代表はthread per connectionモデルであり、プライマリスレッド上のaccept呼び出しの戻りをブロックするたびに、新しいsocketの読み取り/書き込みにサービスする新しいスレッドを作成します.このモデルの利点はプログラムが簡潔で、作成が簡単であることである.欠点は、伸縮性がスレッド数の制限を受け、接続が多くなるとスレッドも多くなり、頻繁なスレッド切り替えではパフォーマンスが大幅に低下することです.
  • 非同期モデル:
  • 非同期モデルは一般に非ブロックIOモードを用い,epoll/select/pollなどの多重化機構と組み合わせた.非ブロッキングモードでreadが呼び出され、データ読み取り可能でない場合は、現在のスレッドをブロッキングするのではなく、すぐに戻って、ユーザに読み取り可能でないことを通知する(EAGAIN/EWOULDBLOCK).非同期モデルは、1つのスレッドを複数のIOオブジェクトに同時にサービスさせることができる.
    非同期モデルの典型的な代表はreactorモデルである.reactorモデルでは、処理するIOイベントをすべて1つの中心のIOマルチプレクサ(一般的にepoll/select/poll)に登録し、プライマリスレッドがマルチプレクサにブロックされます.IOイベントが到来するか、または準備が完了すると、マルチプレクサは戻り、対応するIOイベントを対応するプロセッサ(すなわちコールバック関数)に配布し、最後にプロセッサはread/write関数を呼び出してIO動作を行う.
    非同期モデルの特徴は、同期モデルよりもパフォーマンスと伸縮性が優れていることですが、構造が複雑で、作成とメンテナンスが容易ではありません.非同期モデルでは,IO以前のコード(IOタスクのコミット者)とIO後の処理コード(コールバック関数)が切り離されている.
    コラボレーションとネットワークプログラミング
    コンシステントは同期モデルと非同期モデルの欠点を克服し,彼らの利点と結びつけて可能性を提供した:現在,我々は3つのコンシステントA,B,Cがそれぞれ数回IO操作を行うと仮定した.この3つのコパスは、同じスケジューラまたはスレッドのコンテキストで動作し、CPUを順次使用します.スケジューラは、内部でマルチプレクサ(epoll/select/poll)を維持します.
    Aは、まず、IO動作を実行するが、IO動作がすぐに完了しない場合に、そのIOイベントをスケジューラに登録し、CPUを自発的に放棄する.このとき、スケジューラはBをCPUに切り替えて実行を開始し、同様に、IO操作に遭遇したときにIOイベントをスケジューラに登録し、CPUを自発的に放棄する.スケジューラはCをcpuに切り替えて実行を開始します.すべてのコヒーレンスが「ブロック」されると、スケジューラは登録されたIOイベントが発生したか、または準備ができているかを確認します.この時点で、コーディネータBが登録したIO時間が完了すると、スケジューラはBの実行を再開し、Bは前回CPUを放棄した場所から下へ実行されます.AとCは同じです.
    このように、各コヒーレンスにとって、同期モデルである.しかし、アプリケーション全体にとって、非同期のモデルです.
    では、原理は終わりました.実際の例を見てみましょう.echo serverです.
    echo server
    この例では、orchidライブラリを使用してecho serverを記述します.orchidライブラリはboostに基づいて構築されたコラボレーション/ネットワークIOライブラリです.
    echo serverは、まず接続イベントを処理する必要があります.接続イベントを専門に処理するためのコパスを作成します.
    typedef boost::shared_ptr<orchid::socket> socket_ptr; 
    // ACCEPT
    void handle_accept(orchid::coroutine_handle co) {
    try {
             orchid::acceptor acceptor(co -> get_scheduler().get_io_service());// acceptor
    acceptor.bind_and_listen("5678",true);
             for(;;) {
                socket_ptr sock(new orchid::socket(co -> get_scheduler().get_io_service()));
    acceptor.accept(*sock,co);
                // socket。 main ,
                co -> get_scheduler().spawn(boost::bind(handle_io,_1,sock),orchid::minimum_stack_size());
    }
    } catch(boost::system::system_error& e) {
                cerr<<e.code()<<" "<<e.what()<<endl;
    }
    }

    orchidでは、handle_などの関数署名void(orchid::coroutine_handle)を満たす必要があります.acceptに示すように、パラメータcoは現在の関数が位置するコヒーレントハンドルであり、現在の関数が位置するコヒーレントを表す.
    上記のコードでは、acceptorを作成し、5678ポートをリスニングし、「ブロック」で接続が来るのを待っています.接続イベントが来ると、新しいソケットを作成して新しいsocketにサービスします.ソケットIOの処理の協力は以下の通りである.
    //  SOCKET IO      
    void handle_io(orchid::coroutine_handle co,socket_ptr sock) {
       orchid::tcp_ostream out(*sock,co);
       orchid::tcp_istream in(*sock,co);
       for(std::string str;std::getline(in, str) && out;) {
          out<<str<<endl;
       }
    }

    IO処理コヒーレンスは、まず、TCPの入出力を表す入力ストリームと出力ストリームとを、入力ソケットに作成する.そして、入力ストリームから1行ずつ読み出し、出力ストリームに出力する.SOcket上のTCP接続が切断されると、入力ストリームと出力ストリームのeofフラグがセットされるため、ループが終了し、コヒーレントが終了する.
    orchidは、ユーザがストリームの形式でソケットを操作することができる.入力ストリームと出力ストリームは、それぞれstd::istreamとstd::ostreamのインタフェースを提供します.入力ストリームと出力ストリームはバッファ付きであり、ユーザーがバッファなしの読み書きsocketまたは自己構築バッファを必要とする場合は、orchid::socketのreadおよびwrite関数を直接呼び出すことができます.ただし、この2つの関数はboost::system_を放出することに注意してください.error異常はエラーを表す.
    注意深い読者は気づいたかもしれませんが、handle_ioの関数署名はvoid(orchid::coroutine_handle)を満たさず、handle_に戻ります.acceptでは、実際にboostを使用していることがわかります.bind対handle_io関数は,関数署名の要件を満たすように適合した.
    最後にmain関数です.
    int main() {     
       orchid::scheduler sche;
       sche.spawn(handle_accept,orchid::coroutine::minimum_stack_size());//
       sche.run();
    }

    上記のecho serverの例では、従来のthread per connectionモデルと同様に簡潔で明確であるcoroutine per connectionのプログラミングモデルを採用したが、プログラム全体は実際には同じスレッドで実行されている.
    コヒーレンスの切り替えコストはスレッドよりはるかに小さいため、thread per connectionのモデルでは難しいが、数千のコヒーレンスを同時に起動することができます.性能面では、最下位のIOシステム全体が実際にboostを使用する.asioという高性能の非同期ioライブラリで実現されています.また,IOが費やした時間と比較して,コヒーレントスイッチングのオーバーヘッドは基本的に無視できる.
    従って,コヒーレンスにより,同期IOモデルの簡潔性を維持しながら,非同期IOモデルに近似した高性能を得ることができる.