c++ - para - introducción a android studio incluye proyectos reales y el código fuente pdf
código ordenado para IO asíncrono (5)
Mientras que IO asincrónico (descriptores que no bloquean con select / poll / epoll / kqueue, etc.) no es la cosa más documentada en la web, hay algunos buenos ejemplos.
Sin embargo, todos estos ejemplos, una vez determinados los identificadores devueltos por la llamada, solo tienen un do_some_io(fd)
'' do_some_io(fd)
''. En realidad, no explican cómo abordar mejor el IO asíncrono real en dicho método.
Bloquear IO es muy ordenado y sencillo para 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 quizás (ab) usando los gotos computados de GCC:
#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 tal vez excepciones de C ++ y una máquina de estados, por lo que las funciones de los trabajadores pueden activar el bit de aborto / reanudación, o quizás una máquina de estado controlada por tablas?
No es cómo hacer que funcione, ¡es cómo hacerlo más fácil de mantener que estoy persiguiendo!
Desea desacoplar "io" del procesamiento, momento en el que el código que lee se volverá muy legible. Básicamente tienes:
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, e incluso si tiene múltiples respuestas de solicitud es bastante legible, simplemente "devuelve read_io_event ()" después de establecer un estado o lo que sea.
Existe un gran patrón de diseño "coroutine" para resolver este problema.
Es lo mejor de ambos mundos: código ordenado, exactamente como flujo 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 de código de coroutine:
void do_some_io() {
blocking_read(something,sizeof(something));
blocking_read(something_else,sizeof(something_else));
blocking_write(something,sizeof(something));
}
Parece un código sincrónico, pero de hecho el flujo de control se usa de otra manera, así:
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
Por lo tanto, el planificador de un único subproceso controla muchas corutinas con código definido por el usuario y ordenadas llamadas de tipo síncrono a io.
El ejemplo de implementación de C ++ coroutines es "boost.coroutine" (en realidad no forma parte de boost :) http://www.crystalclearsoftware.com/soc/coroutine/ Esta biblioteca implementa completamente la mecánica de coroutine y puede usar boost.asio como planificador y asincrónico. capa.
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.
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.
Puedes escribir:
async_schedule(callback, arg, timeout);
O:
async_wait(condition, callback, arg, timeout);
Entonces su condición incluso puede configurarse en otro hilo (siempre que se ocupe de la seguridad del hilo al acceder a esa variable).
Implementé un marco asíncrono en C para mi proyecto incrustado porque quería tener una multitarea no preventiva 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
Sugiero echar un vistazo en: http://www.kegel.com/c10k.html , segundo echar 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 al sistema:
- seleccionar es reactor simple
- Los epoll tienen interfaz activada por flanco o por nivel que requieren un enfoque diferente
- iocp es un proactor requiere otro enfoque
Sugerencia: utilice una buena biblioteca existente como Boost.Asio para C ++ o libevent para C.
EDITAR: Así es como ASIO se encarga de esto
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 a que ASIO funciona como proactor, le notifica cuando la operación está completa y maneja EWOULDBLOCK internamente.
Si dices como reactor, puedes 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 como eso.