tidy code for asynchronous IO

后端 未结 5 1065
醉话见心
醉话见心 2020-12-12 13:15

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.

<
相关标签:
5条回答
  • 2020-12-12 13:48

    You want to decouple "io" from processing, at which point the code you read will become very readable. Basically you have:

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

    ...then the real code is in process event, and even if you have multiple requests responses it's pretty readable, you just do "return read_io_event()" after setting a state or whatever.

    0 讨论(0)
  • 2020-12-12 13:53

    State machines are one nice approach. It's a bit of complexity up front that'll save you headaches in the future, where the future starts really, really soon. ;-)

    Another method is to use threads and do blocking I/O on a single fd in each thread. The trade-off here is that you make I/O simple but may introduce complexity in synchronization.

    0 讨论(0)
  • 2020-12-12 13:53

    Great design pattern "coroutine" exists to solve this problem.

    It's the best of both worlds: tidy code, exactly like synchronous io flow and great performance without context switching, like async io gives. Coroutine looks inside like an odinary synchronous thread, with single instruction pointer. But many coroutines can run within one OS thread (so-called "cooperative multitasking").

    Example coroutine code:

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

    Looks like synchronous code, but in fact control flow use another way, like this:

    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 
    

    So single threaded scheduler control many coroutines with user-defined code and tidy synchronous-like calls to io.

    C++ coroutines implementation example is "boost.coroutine" (actually not a part of boost :) http://www.crystalclearsoftware.com/soc/coroutine/ This library fully implements coroutine mechanics and can use boost.asio as scheduler and async io layer.

    0 讨论(0)
  • 2020-12-12 13:58

    I suggest take a look on: http://www.kegel.com/c10k.html, second take a look on existing libraries like libevent, Boost.Asio that already do the job and see how they work.

    The point is that the approach may be different for each type of system call:

    • select is simple reactor
    • epoll have both edge or level triggered interface that require different approach
    • iocp is proactor require other approach

    Suggestion: use good existing library like Boost.Asio for C++ or libevent for C.

    EDIT: This is how ASIO handles this

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

    Because ASIO works as proactor it notifies you when operation is complete and handles EWOULDBLOCK internally.

    If you word as reactor you may simulate this behavior:

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

    Something like that.

    0 讨论(0)
  • 2020-12-12 13:58

    You need to have a main loop that provides async_schedule(), async_foreach(), async_tick() etc. These functions in turn place entries into a global list of methods that will run upon next call to async_tick(). Then you can write code that is much more tidy and does not include any switch statements.

    You can just write:

    async_schedule(callback, arg, timeout); 
    

    Or:

    async_wait(condition, callback, arg, timeout); 
    

    Then your condition can even be set in another thread (provided that you take care of thread safety when accessing that variable).

    I have implemented an async framework in C for my embedded project because I wanted to have non-preemptive multitasking and async is perfect for doing many tasks by doing a little bit of work during every iteration of the main loop.

    The code is here: https://github.com/mkschreder/fortmax-blocks/blob/master/common/kernel/async.c

    0 讨论(0)
提交回复
热议问题