web-dev-qa-db-ja.com

非同期の整頓されたコードIO

非同期IO(select/poll/epoll/kqueueなどの非ブロッキング記述子)はWebで最も文書化されているものではありませんが、いくつかの良い例があります。

ただし、これらの例はすべて、呼び出しによって返されるハンドルを決定しているため、「do_some_io(fd)」スタブがあります。それらは、そのような方法で実際の非同期IOに最もよくアプローチする方法を実際には説明していません。

ブロッキングIOは、コードを読むのに非常に整然としていて簡単です。非ブロッキング、非同期IOは、一方で、毛むくじゃらで厄介です。

どのようなアプローチがありますか?堅牢で読みやすいものは何ですか?

void do_some_io(int fd) {
  switch(state) {
    case STEP1:
       ... async calls
       if(io_would_block)
          return;
       state = STEP2;
    case STEP2:
       ... more async calls
       if(io_would_block)
          return;
       state = STEP3;
    case STEP3:
       ...
  }
}

またはおそらく(ab)GCCの計算されたgotoを使用する:

#define concatentate(x,y) x##y
#define async_read_xx(var,bytes,line)       \
   concatentate(jmp,line):                  \
   if(!do_async_read(bytes,&var)) {         \
       schedule(EPOLLIN);                   \
       jmp_read = &&concatentate(jmp,line); \
       return;                              \
}

// macros for making async code read like sync code
#define async_read(var,bytes) \
    async_read_xx(var,bytes,__LINE__)

#define async_resume()            \
     if(jmp_read) {               \
         void* target = jmp_read; \
         jmp_read = NULL;         \
         goto *target;            \
     }

void do_some_io() {
   async_resume();
   async_read(something,sizeof(something));
   async_read(something_else,sizeof(something_else));
}

または、おそらくC++例外とステートマシンなので、ワーカー関数はアボート/レジュームビットをトリガーできますか、それともテーブル駆動のステートマシンですか?

それを機能させる方法ではなく、私が追いかけていることを維持可能にする方法です!

24
Will

http://www.kegel.com/c10k.html を見てみることをお勧めします。次に、libevent、Boost.Asioなど、すでに機能している既存のライブラリを調べて、それらがどのように機能するかを確認します。作業。

重要なのは、システムコールのタイプごとにアプローチが異なる可能性があるということです。

  • selectは単純なリアクターです
  • epollには、異なるアプローチを必要とするエッジまたはレベルトリガーインターフェイスの両方があります
  • iocpはproactorであり、他のアプローチが必要です

提案:C++の場合はBoost.Asio、Cの場合はlibeventなどの既存の優れたライブラリを使用してください。

編集:これはASIOがこれを処理する方法です

class connection {
   boost::asio:ip::tcp::socket socket_;
public:
   void run()
   {
         // for variable length chunks
         async_read_until(socket_,resizable_buffer,'\n',
               boost::bind(&run::on_line_recieved,this,errorplacehplder);
         // or constant length chunks
         async_read(socket_,buffer(some_buf,buf_size),
               boost::bind(&run::on_line_recieved,this,errorplacehplder);
   }
   void on_line_recieved(error e)
   {
        // handle it
        run();
   }

};

ASIOはプロクターとして機能するため、操作が完了すると通知し、内部でEWOULDBLOCKを処理します。

リアクターとしてWordを使用する場合は、この動作をシミュレートできます。

 class conn {
    // Application logic

    void run() {
       read_chunk(&conn::on_chunk_read,size);
    }
    void on_chunk_read() {
         /* do something;*/
    }

    // Proactor wrappers

    void read_chunk(void (conn::*callback),int size, int start_point=0) {
       read(socket,buffer+start,size)
       if( complete )
          (this->*callback()
       else {
          this -> tmp_size-=size-read;
          this -> tmp_start=start+read;
          this -> tmp_callback=callback
          your_event_library_register_op_on_readable(callback,socket,this);
       }
    }
    void callback()
    {
       read_chunk(tmp_callback,tmp_size,tmp_start);
    }
 }

そんな感じ。

17
Artyom

ステートマシンは1つの優れたアプローチです。少し複雑なことで、将来の頭痛の種を減らすことができます。将来は本当に、本当にすぐに始まります。 ;-)

別の方法は、スレッドを使用して、各スレッドの単一のfdでブロッキングI/Oを実行することです。ここでのトレードオフは、I/Oを単純にすることですが、may同期が複雑になります。

6
dwc

この問題を解決するための優れたデザインパターン「コルーチン」が存在します。

これは両方の長所です。同期ioフローとまったく同じように整頓されたコードと、非同期ioが提供するようなコンテキスト切り替えなしの優れたパフォーマンスです。コルーチンは、単一の命令ポインターを備えた通常の同期スレッドのように見えます。ただし、多くのコルーチンは1つのOSスレッド内で実行できます(いわゆる「協調マルチタスク」)。

コルーチンコードの例:

void do_some_io() {
   blocking_read(something,sizeof(something));
   blocking_read(something_else,sizeof(something_else));
   blocking_write(something,sizeof(something));
}

同期コードのように見えますが、実際には、制御フローは次のような別の方法を使用します。

void do_some_io() {
   // return control to network io scheduler, to handle another coroutine
   blocking_read(something,sizeof(something)); 
   // when "something" is read, scheduler fill given buffer and resume this coroutine 

   // return control to network io scheduler, to handle another coroutine
   CoroSleep( 1000 );
   // scheduler create async timer and when it fires, scheduler pass control to this coroutine
    ...
   // and so on 

そのため、シングルスレッドスケジューラは、ユーザー定義のコードとioへの整然とした同期のような呼び出しで多くのコルーチンを制御します。

C++コルーチンの実装例は「boost.coroutine」です(実際にはboostの一部ではありません:) http://www.crystalclearsoftware.com/soc/coroutine/ このライブラリはコルーチンメカニズムを完全に実装しており、boostを使用できます。スケジューラーおよび非同期ioレイヤーとしての.asio。

3
Maxim Ky

Async_schedule()、async_foreach()、async_tick()などを提供するメインループが必要です。これらの関数は、次にasync_tick()を呼び出したときに実行されるメソッドのグローバルリストにエントリを配置します。次に、はるかに整頓され、switchステートメントを含まないコードを記述できます。

あなたはただ書くことができます:

async_schedule(callback, arg, timeout); 

または:

async_wait(condition, callback, arg, timeout); 

次に、条件を別のスレッドに設定することもできます(ただし、その変数にアクセスするときにスレッドの安全性に注意を払う必要があります)。

組み込みプロジェクト用に非同期フレームワークをCで実装しました。これは、プリエンプティブでないマルチタスクが必要であり、非同期は、メインループの反復ごとに少しの作業を行うことで、多くのタスクを実行するのに最適です。

コードはここにあります: https://github.com/mkschreder/fortmax-blocks/blob/master/common/kernel/async.c

1
user2826084

「io」を処理から切り離したいのですが、その時点で、読んだコードは非常に読みやすくなります。基本的にあなたは持っています:


    int read_io_event(...) { /* triggers when we get a read event from epoll/poll/whatever */

     /* read data from "fd" into a vstr/buffer/whatever */

     if (/* read failed */) /* return failure code to event callback */ ;

     if (/* "message" received */) return process_io_event();

     if (/* we've read "too much" */) /* return failure code to event callback */ ;

     return /* keep going code for event callback */ ;
    }


    int process_io_event(...) {
       /* this is where you process the HTTP request/whatever */
    }

...実際のコードは処理中のイベントであり、複数のリクエストレスポンスがある場合でも、かなり読みやすく、状態などを設定した後に「returnread_io_event()」を実行するだけです。

0
James Antill