2009-05-19 9 views
19

Si bien el IO asíncrono (descriptores no bloqueantes con select/poll/epoll/kqueue, etc.) no es la cosa más documentada en la web, hay algunos buenos ejemplos.código de orden para IO asíncrono

Sin embargo, todos estos ejemplos, habiendo determinado los identificadores devueltos por la llamada, solo tienen un resguardo 'do_some_io(fd)'. En realidad, no explican cómo abordar mejor el IO asíncrono real en dicho método.

El bloqueo de IO es muy ordenado y sencillo de leer el código. Sin bloqueo, Async IO es, por otro lado, peludo y desordenado.

¿Qué enfoques hay? ¿Qué son robustos y legibles?

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: 
     ... 
    } 
} 

o tal vez (ab) uso de los goto de GCC calculadas:

#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)); 
} 

o quizás excepciones de C++ y una máquina de estados, por lo que las funciones de los trabajadores pueden desencadenar el bit de interrupción/reanudación, o tal vez una tabla impulsada ¿máquina estatal?

No es cómo hacer que funcione, es cómo hacerlo más fácil de mantener que estoy persiguiendo!

Respuesta

16

sugiero echar un vistazo en: http://www.kegel.com/c10k.html, segundo eche un vistazo a las bibliotecas existentes como libevent, Boost.Asio que ya hacen el trabajo y ver cómo funcionan.

El punto es que el enfoque puede ser diferente para cada tipo de llamada de sistema:

  • seleccione es reactor sencillo
  • epoll han tanto borde o nivel de interfaz que requieren enfoque diferente
  • IOCP se dispara proactor requerir otro enfoque

sugerencia: utilizar buena biblioteca existente como Boost.Asio para C++ o libevent para C.

EDIT: Esta es la forma ASIO se encarga de esta

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(); 
    } 

}; 

Debido ASIO trabaja como proactor le avisa cuando la operación se ha completado y maneja EWOULDBLOCK internamente.

Si palabra como reactor puede simular este comportamiento:

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); 
    } 
} 

Algo por el estilo.

+0

El libevent buffer eventos envoltorios, con altas y bajas incluso marcas de agua , son una forma conveniente de evitar el manejo IO arenoso; pero, ¿cómo se representa este estado en el código que lo llama y que debe ser reanudable? – Will

+0

EDITADO: Agregué un ejemplo – Artyom

+0

gracias Artyom, espero que la gente busque en Google epoll y ¡así lo encuentran! – Will

5

Las máquinas de estado son un buen enfoque. Es un poco complejo en el frente que te ahorrará dolores de cabeza en el futuro, donde el futuro comienza muy, muy pronto. ;-)

Otro método es usar hilos y bloquear E/S en un solo fd en cada hilo. La compensación aquí es que hace que la E/S sea simple, pero puede introducir complejidad en la sincronización.

+11

Un ejemplo rápido de un estado de la máquina asíncrona para io sería útil – Will

+0

me gustaría ver ese ejemplo también – Viet

0

Desea desacoplar "io" del procesamiento, momento en el que el código que lee será muy legible.Básicamente, usted tiene:


    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 */ 
    } 

... entonces el código real está en proceso de evento, e incluso si tiene varias solicitudes de respuestas es bastante fácil de leer, que acaba de hacer "read_io_event() devuelven" después de establecer un estado o lo que sea .

+2

crear un buffer funciona lo suficientemente bien en una línea o nivel de mensaje; pero cuando está analizando algo que es más complejo, ¿cómo se representa este estado en el controlador process_io_event()? – Will

3

Existe un gran patrón de diseño "coroutine" para resolver este problema.

Es lo mejor de ambos mundos: código ordenado, exactamente como el flujo io sincrónico y gran rendimiento sin cambio de contexto, como async io da. Coroutine mira hacia adentro como un hilo síncrono odinario, con un único puntero de instrucción. Pero muchas corutinas pueden ejecutarse dentro de un subproceso del sistema operativo (denominada "multitarea cooperativa").

Ejemplo código corrutina:

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

Parece código síncrono, pero en flujo de control hecho de utilizar de otra manera, como esto:

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 

Así solo control planificador roscado muchos corrutinas con definido por el usuario codificar y ordenar llamadas de tipo síncrono a io.

ejemplo C++ aplicación corrutinas es "boost.coroutine" (en realidad no es una parte del impulso :) http://www.crystalclearsoftware.com/soc/coroutine/ Esta biblioteca implementa totalmente la mecánica co-rutina y se puede utilizar boost.asio como planificador y asíncrono io capa.

+0

[Boost.Coroutine] (http://www.boost.org/doc/libs/release/libs/coroutine/doc/html/index.html) ahora es parte del impulso. – Mankarse

1

Necesita tener un bucle principal que proporcione async_schedule(), async_foreach(), async_tick() etc. Estas funciones a su vez colocan las entradas en una lista global de métodos que se ejecutarán en la siguiente llamada a async_tick(). Luego puede escribir código que sea mucho más ordenado y no incluya ninguna instrucción de cambio.

Usted puede escribir:

async_schedule(callback, arg, timeout); 

O:

async_wait(condition, callback, arg, timeout); 

Luego, su estado, incluso se puede configurar en otro hilo (siempre y cuando se toma el cuidado de la seguridad hilo cuando acceder a esta variable).

He implementado un marco asíncrono en C para mi proyecto integrado porque quería tener tareas múltiples no preventivas y la función asincrónica es perfecta para realizar muchas tareas haciendo un poco de trabajo durante cada iteración del ciclo principal.

el código está aquí: https://github.com/mkschreder/fortmax-blocks/blob/master/common/kernel/async.c

+1

El enlace ya no funciona. –

Cuestiones relacionadas