上次写Blog的时候Proactor并没有说的十分清楚。
这次用aio_read工作流程来举个例子:
- 主线程调用aio_read函数向内核注册fd的读完成事件以及buffer,期望读取多少字节,偏移是多少和怎么回馈给应用程序(Linux上一般都是信号)。
- 主程序继续执行自己的业务,不需要等待aio_read。这时候aio_read存在两种可能性,一种是数据已经读入buffer,另一种书数据还没读入buffer。
- 当fd的数据读取到用户缓冲区之后,内核立刻通知程序数据读取完成(Linux上一般是信号),需要程序进行处理。
在Erlang中并没有使用aio系列函数,而是使用的模拟Proactor。
先让我说下什么是模拟Proactor,模拟Proactor就是使用一个线程不断的使用select/epoll/kevent等函数等待IO事件,并且当IO事件发生后完成所对应的读写操作,并且通过队列的方式将结果送到工作线程上
为什么Erlang选择了模拟Proactor模式,这个和Erlang的整个模型是息息相关的。Erlang使用的是actor模型,所有的东西都被映射为Erlang的进程,这些进程之间的通信是通过消息进行的。Erlang为了执行它自己的Erlang进程,就需要将Erlang进程和物理线程关联起来,就要自己实现调度,而这个调度是在用户层完成的。所以Erlang的scheduler本身是一个没有阻塞行为的线程(除休眠状态外),它不断的去检查Erlang进程队列,如果发现有Erlang进程可执行,就执行该任务的OPcode或者BIF来完成相应的功能。那么这时候就产生了一个问题,网络IO的select操作,文件读写的阻塞操作会阻碍scheduler检查Erlang的进程队列,那该怎么办?Erlang的大神很自然的想到了模拟Proactor模式。Erlang的inet是一个内连驱动,内部实现了一个队列,当一个Erlang进程发起IO操作的时候,就将这个操作进行一下封装放入这个队列中,等待IO线程被唤醒的时候去执行。当数据读取成功后,IO线程将所有的数据读取好作为一个消息放入相对应的Erlang进程的信箱中,这样当scheduler去再次检查这个进程的时候,就可以进行后续的业务处理。这样IO和业务处理就被很好的分离开来。