Whilst asynchronous IO (non-blocking descriptors with select/poll/epoll/kqueue etc) is not the most documented thing on the web, there are a handful of good examples.
However, all these examples, having determined the handles that are returned by the call, just have a 'do_some_io(fd)
' stub. They don't really explain how to best approach the actual asynchronous IO in such a method.
Blocking IO is very tidy and straightforward to read code. Non-blocking, async IO is, on the other hand, hairy and messy.
What approaches are there? What are robust and readable?
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:
...
}
}
or perhaps (ab)using GCC's computed gotos:
#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));
}
Or perhaps C++ exceptions and a state machine, so worker functions can trigger the abort/resume bit, or perhaps a table-driven state-machine?
Its not how to make it work, its how to make it maintainable that I'm chasing!
See Question&Answers more detail:
os 与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…