11. IO各种模型
Last updated
Was this helpful?
Last updated
Was this helpful?
我知道我讲了一些玄学东西, 上一节在对比进程/线程的时候我特意留了一嘴说redis/memcached, 立刻我就意识到如果说了这两者的线程模型, 那么是不可能说他们的IO模型的, 因为他们所表现出来的特性是由线程与IO两种模型结合在一起决定的.
我们从一个最简单的情况开始讲, 假设你新建了一个socket监听某端口 (tcpudp都行), 如果有人给你发消息, 消息就会从网卡哪儿先被拷贝到内核缓冲区里, 然后你从内核区域里[读]到进程的虚拟内存下. socket描述符你已经有了, 如果你现在就要读, 会发生什么呢? 这就是IO模型最核心的问题 → 会发生什么
在你什么都不做的情况下, 一个socket默认是堵塞型的, 意思就是如果你现在通过 recvfrom 函数从这个socket里读数据, 如果有数据就直接返回, 如果没有数据就一直卡在哪儿, 一直卡到有数据再返回 (消息从网卡拷贝到内核里)
但你也可以选择把这个socket设置成非阻塞模式, 此时在网卡没有收到数据之前, 你一调用recvfrom就会立刻返回并报错, 你就在一个for循环里一直查(这种反复查的动作我们叫它polling), 直到网卡已经收到数据了, 此时不立刻报错了, 内核开始将数据拷贝到内核buffer里, 然后还给你的程序.
非阻塞IO确实不会产生恶心的阻塞, 但是为什么很少见到呢? 因为如果你是阻塞状态, CPU就不会让你跑, 但是在非阻塞的IO下, CPU还得让你一直跑着, 效果都是收信息, 后者还得浪费CPU的时间
这个模型不是针对某单个socket了, 这个模型处理一组socket, 以select函数为例, select函数接受一组socket, 函数调用以后就卡着, 直到里面某一个socket变的可读了就返回这个可读的socket并同时解除阻塞.
那么既然你已经持有一个可读的socket了, 你就可以通过函数 recvfrom 去里面读东西了. multiplexing 里面大有玩头, 另类玩法比如poll/epoll的对比(这个我们会在下一节讲), 正是这个原因促成了redis如此出色的表现
既然内核知道什么时候可读, 那为什么不找一种方式来通知我们呢? 信号驱动IO要求你先通过sigaction函数为这个socket注册上信号, 等网卡收到数据了, 也拷贝到内核buffer里了, 这个时候通过一个SIGIO信号告诉你的程序, 这个时候你的程序再来通过 recvfrom 的方式去取
这种IO模型是5个模型里唯一真正异步IO的模型, 我们首先通过 aio_read 的方式传入
一个socket(fd)
一个byte数组(buffer) + 数组长度
就绪以后如何通知我们 (比如golang的 done<-struct{}{}
)
接下来是纯自动的: 网卡收到数据 → 拷贝到内核缓冲区 → 从内核缓冲区读到你的byte数组里 → 通知你, 纯自动纯的, 这么纯自动的东西为什么很少用呢? 因为不是很多系统都支持AsyncIO的
"异步的" IO: 在调用读请求的时候不会产生阻塞, 仅有最后一种Model5- AIO是符合要求的
"同步的" IO: 在调用读请求的时候会产生阻塞, 直到拷贝到byte数组的动作完全搞定, 前四种全都是同步的, 因为尽管通知(内核buffer已就绪的)机制各有不同, 但是实际从内核buffer拷贝到用户byte数组的时候, 还是阻塞的, 一直阻塞到拷贝完全ok位置
本文译自: 这里
这是一篇非常好的文章, 阅读完我只用了半小时
但感觉, 非常的澄澈