IO模型分析

何为同步与异步

在LNMP的生态当中我们基本上用到的是同步操作.例如PHP的file_get_contents函数就是一个典型的同步任务.而Node.js当中的回调模式是一个典型的异步模式如Promise.

同步

同步可以理解为:发送一个系统调用,并等待系统调用的返回.

异步

异步可以理解为:发送一个系统调用,不等待系统返回可以继续处理当前的任务,等待系统处理完毕的回调.

不同的IO模型

阻塞与非阻塞强调的是当前进程或者是线程的状态.

阻塞IO

在这里插入图片描述

从图中可知,数据经历两种变化.
1.数据从没有准备状态到准备完成
2.从内核态拷贝数据到用户态.

非阻塞IO

在这里插入图片描述

发起recvfrom调用后内核在准备数据.
同步不断的轮训,发现数据准备完成后,将数据从内核态拷贝到用户态.

多路复用IO

在这里插入图片描述
IO多路复用:无需使用polling或者多线程就可以"同时"(指时间段)处理多个文件描述符.但是需要注意从内核读取数据还是同步的.
经典的select与epoll都是IO多路复用.图中描述的是select模型.epoll询问方式和select有很大不同.

epoll与select的区别
  • epoll要比select高效
  • select的支持最大文件描述符有限,epoll没有限制

select:每次返回准备好的文件描述数量.调用方自己遍历所有的FD,判断是否准备好,准备好然后再读数据.
select的最大的问题,我们不知道那些FD是否准备好,只能去遍历.
假设有100个FD,只有1个准备好,可想而知,相当于一个顺序查找,效率低下.
因此高效的epoll就出现了,只告诉调用方准备好的FD


    fd_set readfds, writefds;
    FD_ZERO(&readfds);
    FD_ZERO(&writefds);
    for (int i=0; i < read_fd_count; i++)
      FD_SET(my_read_fds[i], &readfds);
    for (int i=0; i < write_fd_count; i++)
      FD_SET(my_write_fds[i], &writefds);

    struct timeval timeout;
    timeout.tv_sec = 3;
    timeout.tv_usec = 0;

    int num_ready = select(FD_SETSIZE, &readfds, &writefds, NULL, &timeout);

    if (num_ready < 0) {
      perror("error in select()");
    } else if (num_ready == 0) {
      printf("timeout\n");
    } else {
      for (int i=0; i < read_fd_count; i++)
        if (FD_ISSET(my_read_fds[i], &readfds))
          printf("fd %d is ready for reading\n", my_read_fds[i]);
      for (int i=0; i < write_fd_count; i++)
        if (FD_ISSET(my_write_fds[i], &writefds))
          printf("fd %d is ready for writing\n", my_write_fds[i]);
    }

epoll不是POSIX标准.但却在Linux当中被广泛使用.
epoll:只返会准备好的FD.

#define MAX_EVENTS 10
struct epoll_event ev, events[MAX_EVENTS];
int listen_sock, conn_sock, nfds, epollfd;

/* Code to set up listening socket, 'listen_sock',
  (socket(), bind(), listen()) omitted */

epollfd = epoll_create1(0);//创建epoll
if (epollfd == -1) {
   perror("epoll_create1");
   exit(EXIT_FAILURE);
}

ev.events = EPOLLIN;
ev.data.fd = listen_sock;
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, listen_sock, &ev) == -1) {//监听指定的listen_sock
   perror("epoll_ctl: listen_sock");
   exit(EXIT_FAILURE);
}

for (;;) {
   nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1);//循环取事件,放入event事件
   if (nfds == -1) {
       perror("epoll_wait");
       exit(EXIT_FAILURE);
   }

   for (n = 0; n < nfds; ++n) {
       if (events[n].data.fd == listen_sock) {
           conn_sock = accept(listen_sock,
                              (struct sockaddr *) &addr, &addrlen);//接收客户端连接
           if (conn_sock == -1) {
               perror("accept");
               exit(EXIT_FAILURE);
           }
           setnonblocking(conn_sock);// ET模式下需要设置为非阻塞模式
           ev.events = EPOLLIN | EPOLLET;// ET模式下
           ev.data.fd = conn_sock;
           if (epoll_ctl(epollfd, EPOLL_CTL_ADD, conn_sock,
                       &ev) == -1) {//添加客户端的事件到当前的epoll
               perror("epoll_ctl: conn_sock");
               exit(EXIT_FAILURE);
           }
       } else {
           do_use_fd(events[n].data.fd); //使用新的准备好的文件描述符不停接收数据,并在这里记录当前读取,写入的数据
       }
   }
}

信号驱动IO

在这里插入图片描述

信号驱动IO无需等待数据准备好,系统会发送信号SIGIO回调handler,然后读取数据.
这时候IO是同步读取,从内核态到用户态.

异步IO

在这里插入图片描述

真正的异步IO,从图中很明显看出数据已经被拷贝到了用户态,这是与信号IO模型最大的区别.

IO模型比较

在这里插入图片描述

从图中我可以得出:

  • 异步IO:不会导致线程阻塞.
  • 同步IO:线程一直阻塞到数据IO操作完成.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值