网络编程IO -- 同步,异步,阻塞,非阻塞

0. IO基础

0.1 计算机IO控制方式

  • 询问方式(CPU和IO设备串行工作)
a. 查询指令,用来查询设备是否就绪
b. 传送指令,设备就绪时,执行数据交换
c. 转移指令,设备未就绪时,执行转移指令转向查询指令继续查询
缺点:CPU的反复查询过程,浪费时间;且CPU需参与数据的传送过程
  • 中断方式(CPU和IO设备可实现部分并行)
a. 外围设备仅当操作正常或异常结束时,才中断中央处理机
b. CPU不再查询I/O设备是否就绪,仅在响应了I/O中断请求后,转至中断处理程序执行
缺点:输入输出操作由CPU控制,每传送一个字符或一个字,都会发生一次中断;因此只是实现了CPU和I/O的部分并行
  • DMA方式(I/O设备直接与主存交换数据,而不占用CPU)
a. CPU计算文件地址 -> 委派DMA读取文件 -> DMA接管总线 -> DMA读完文件后通知CPU(中断异常)
b. 相比中断方式:CPUIO的干预从字为单位减少到以数据块为单位;且不需要数据拷贝
c. 缺点:每发出一条IO指令,只能读写一个数据块;当需读写多个离散的数据块,并将其传送到不同的主存区域时,需要多条IO指令
  • 通道方式(具有特殊功能的处理器)

0.2 用户态与内核态

a. 用户态运行的上层应用程序 系统调用 内核态资源
b. 系统调用是操作系统最小功能单位,fork/clone/wait/

1. 同步,异步,阻塞,非阻塞

  • 根据消息通知机制:同步/异步

    同步:等待

    异步:注册回调

  • 根据等待消息通知的状态:阻塞/非阻塞

    阻塞:当前线程挂起等待

    非阻塞:执行其他消息处理

    考虑CPU利用率 与 系统线程切换成本

    类型 线程状态 线程获取IO操作结果方式
    同步阻塞 挂起 等待
    同步非阻塞 可执行其他 询问
    异步阻塞 挂起 通知或回调
    异步非阻塞 可执行其他 通知或回调

2. 网络IO —— 5种io模型

阻塞的两个场景:
a. 连接过程,connect()
客户端发起connect请求,服务端accept时,其他客户端的请求会被阻塞  
b. IO过程
IO执行的两个阶段:等待数据和拷贝数据

2.1 阻塞IO


用户空间的应用程序执行一个系统调用,会导致应用程序阻塞,直到数据准备好,并且将数据从内核复制到用户进程,最后进程再处理数据,在等待数据到处理数据的两个阶段,整个进程都被阻塞。
调用应用程序处于一种不再消费 CPU 而只是简单等待响应的状态,请求延迟短

请求连接量增加,线程池/连接池
存在问题:大规模请求

2.2 非阻塞IO

  • 系统调用后,进程并没有被阻塞,内核立即返回给进程,如果数据没有准备好,返回error
  • 重复请求(轮询),将等待数据过程中整段时间的阻塞变为小的阻塞
  • 可以在轮询的间隙执行其他任务,相应的任务完成的响应延迟会增大
  • 轮询检查对CPU的消耗

2.3 多路复用IO/事件驱动IO

  • select, poll, epoll
  • select可同时对多个IO端口监听,当某个socket有数据到达时,通知用户进程

存在问题:  
select需要探测的句柄值较大
某个事件响应过长时,对事件探测的影响

* epoll,更高效的实现

2.4 信号驱动IO

  • 内核数据准备好后,使用信号通知用户进程
  • 拷贝数据过程仍是阻塞的

2.5 异步IO(内核2.6以上支持)

异步IO

  • 内核接收到异步read后,会立刻返回,不会产生阻塞;当数据准备完成并拷贝到用户内存后,内核给用户进程发一个signal或一个基于线程的回调函数
  • 从kernel的角度,当它受到一个asynchronous read之后,首先它会立刻返回,所以
  • aio_read函数会指定通知方式

3. 多路复用IO,epoll

3.1 等待队列

等待队列实现了在事件上的条件等待: 希望等待特定事件的进程把自己放进合适的等待队列,并放弃控制权
等待队列即一组睡眠的进程,当某一条件为真时,由内核唤醒它们

3.2 select/poll

  • select支持的文件描述符默认是1024
  • 每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大
  • 每次调用select,都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大(如服务器有10w连接,某一时间只有一个连接向服务器发送数据,仍会循环10w次,浪费CPU资源)
  • 每次调用开始时,要把current进程放入各个文件描述符的等待队列。在调用结束后,又把进程从各个等待队列中删除
select源码分析:http://zhangyafeikimi.iteye.com/blog/248815

3.3 epoll

可以维持无限数量的连接,无需轮询;select和poll都只提供了一个函数,select或者poll函数。而epoll提供了三个函数,epoll_create,epoll_ctl和epoll_wait
epoll具体实现:

(1). int epoll_create(int size)
    创建一个epoll句柄
    param size: 监听fd的数目
    return: 创建epoll实例的文件描述符
(2). int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
    注册要监听的事件类型
    param epfd:create的返回值
    param op: 动作(注册,修改,删除fd监听事件)
    param fd: 监听fd
    param event: 需要监听的事件
(3). int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
    等待事件的产生
    return :准备好的文件fd
    检测到事件,就将所有就绪的事件从内核事件表中复制到events指向的数组中
  • epoll_ctl时把current在各个文件描述符的等待队列里挂一遍,并为每个fd指定一个回调函数;当设备就绪,唤醒等待队列上的等待者时,就会调用这个回调函数,而这个回调函数会把就绪的fd加入一个就绪链表
  • epoll_wait只需要判断就绪链表是否为空,不需遍历所有fd,不为空则从内核态拷贝少量句柄到用户态
  • epoll注册时(epoll_ctl),一次拷贝

4. swoole

4.1 reactor模型

  • 事件驱动模型
  • 主要操作
a. add 添加socket到reactor
b. set 修改事件监听,可设置监听类型。如对于listen socket监听新连接进行accept
c. del 从reactor移除,不再监听
d. callback事件发生后的处理逻辑
  • 一个或多个并发输入源 - service handler - request handler(处理事件请求)
  • reactor模型:epoll 与 线程池

  • todo

swoole简单实践:
http://blog.jobbole.com/98986/
https://github.com/swoole/swoole-src
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章