C++ 异步 IO 的整洁代码

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/883156/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 17:47:12  来源:igfitidea点击:

tidy code for asynchronous IO

c++casynchronousio

提问by Will

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.

虽然异步 IO(带有 select/poll/epoll/kqueue 等的非阻塞描述符)不是网络上记录最多的东西,但有一些很好的例子。

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.

然而,所有这些例子,在确定了调用返回的句柄后,只有一个“ do_some_io(fd)”存根。他们并没有真正解释如何以这种方法最好地处理实际的异步 IO。

Blocking IO is very tidy and straightforward to read code. Non-blocking, async IO is, on the other hand, hairy and messy.

阻塞 IO 非常整洁且易于阅读代码。另一方面,非阻塞的异步 IO 是多毛和凌乱的。

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:

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

Or perhaps C++ exceptions and a state machine, so worker functions can trigger the abort/resume bit, or perhaps a table-driven state-machine?

或者可能是 C++ 异常和状态机,所以工作函数可以触发中止/恢复位,或者可能是表驱动的状态机?

Its not how to make it work, its how to make it maintainable that I'm chasing!

我追求的不是如何让它工作,而是如何使它可维护!

采纳答案by Artyom

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.

我建议看一看:http: //www.kegel.com/c10k.html,然后再看一下现有的库,比如 libevent、Boost.Asio 已经完成了这项工作,看看它们是如何工作的。

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
  • 选择是简单的反应器
  • epoll 有边缘或水平触发接口,需要不同的方法
  • iocp 是 proactor 需要其他方法

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

建议:使用良好的现有库,如 C++ 的 Boost.Asio 或 C 的 libevent。

EDIT: This is how ASIO handles this

编辑:这就是 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();
   }

};

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

因为 ASIO 作为 proactor 它会在操作完成时通知您并在内部处理 EWOULDBLOCK。

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.

类似的东西。

回答by dwc

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 mayintroduce complexity in synchronization.

另一种方法是使用线程并在每个线程中的单个 fd 上进行阻塞 I/O。此处的权衡是您使 I/O 变得简单,但可能会在同步中引入复杂性。

回答by Maxim Ky

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").

它是两全其美的:整洁的代码,完全像同步 io 流和没有上下文切换的出色性能,就像异步 io 提供的那样。协程内部看起来就像一个普通的同步线程,只有一个指令指针。但是许多协程可以在一个操作系统线程中运行(所谓的“协作多任务”)。

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.

因此,单线程调度程序使用用户定义的代码和整洁的类似同步的 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.

C++ 协程实现示例是“boost.coroutine”(实际上不是 boost 的一部分:) http://www.crystalclearsoftware.com/soc/coroutine/这个库完全实现了协程机制,可以使用 boost.asio 作为调度器和异步 io层。

回答by user2826084

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.

您需要有一个提供 async_schedule()、async_foreach()、async_tick() 等的主循环。这些函数依次将条目放入全局方法列表中,这些方法将在下次调用 async_tick() 时运行。然后,您可以编写更加整洁且不包含任何 switch 语句的代码。

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.

我已经在 C 中为我的嵌入式项目实现了一个异步框架,因为我想要非抢占式多任务处理,并且异步非常适合通过在主循环的每次迭代中做一点工作来完成许多任务。

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

代码在这里:https: //github.com/mkschreder/fortmax-blocks/blob/master/common/kernel/async.c

回答by James Antill

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

您希望将“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 */
    }

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

...然后真正的代码是在处理事件中,即使您有多个请求响应,它也非常可读,您只需在设置状态或其他之后执行“返回 read_io_event()”。