同步、异步和阻塞、非阻塞

Posted by faxiang1230 on May 15, 2018

同步和异步

I/O操作

在UNIX的世界观中万物皆文件,用户程序使用的接口都是通过VFS来实现的,我们知道有三大类型的设备:网络设备,字符设备,块设备, 每一种设备的具体实现都是挂接在VFS的file_operations下的,我们看到的socket,终端、管道、硬盘上的文件都是文件,而针对这些文件的操作称为I/O操作。 根据文件的种类不同,I/O的特征也不一样,像对硬盘上的数据进行的读操作,需要耗费一定的事件才能从硬盘上读到数据,不过一定是确定的;而字符设备和网络设备就不一定了,你可能永远都收不到数据,是不确定。这些不同就需要在程序中采取不同的模型处理

同步异步,阻塞非阻塞区别联系

在这里说的同步与异步是针对应用程序与内核的交互而言的。 同步过程中进程触发IO操作并等待(也就是我们说的阻塞)或者轮询的去查看IO操作(也就是我们说的非阻塞)是否完成。 异步过程中进程触发IO操作以后,直接返回,做自己的事情,IO交给内核来处理,完成后内核通知进程IO完成。

同步和异步针对应用程序来,关注的是程序中间的协作关系;阻塞与非阻塞更关注的是单个进程的执行状态。

同步有阻塞和非阻塞之分,异步没有,它一定是非阻塞的。 阻塞、非阻塞、多路IO复用,都是同步IO,异步必定是非阻塞的,所以不存在异步阻塞和异步非阻塞的说法。真正的异步IO需要CPU的深度参与。 换句话说,只有用户线程在操作IO的时候根本不去考虑IO的执行全部都交给CPU去完成,而自己只等待一个完成信 号的时候,才是真正的异步IO。所以,拉一个子线程去轮询、去死循环,或者使用select、poll、epool,都不是异步。

同步:执行一个操作之后,进程触发IO操作并等待(也就是我们说的阻塞)或者轮询的去查看IO操作(也就是我们说的非阻塞)是否完成 ,等待结果,然后才继续执行后续的操作。
异步:执行一个操作后,可以去执行其他的操作,然后等待通知再回来执行刚才没执行完的操作。
阻塞:进程给CPU传达一个任务之后,一直等待CPU处理完成,然后才执行后面的操作。
非阻塞:进程给CPU传达任我后,继续处理后续的操作,隔断时间再来询问之前的操作是否完成。这样的过程其实也叫轮询。

同步是想要描述两个事情之间有依赖关系,而阻塞是指cpu空等

同步和异步

拿购物来说,现在都有物流快递,你买个东西下了订单之后,就去忙别的事情了,过了两三天之后快递告诉你去东门取快递。购物就是一个典型异步事件。 如果你买了件急需的,平时你该干嘛干嘛,因为你知道急也没用;但是你还是会去官网去查看物流有没有到家门口,到了就直接去取了,这个称之为异步等待。 快递员从仓库里取出东西开始派送,通知你来东门取,然后等了你三个小时,晚上10点你去取了,快递员MMP一声才下班。派送就是同步事件 晚上11点你还没有去取,快递员给你打了个电话让你明天再去取,同步超时

阻塞和非阻塞

你下午4点去东门取快递,快递说马上到,你就站那等他,下午5点他到了,然后你收到了快递。阻塞等待 你下午4点去东门取快递,快递说等会才能到别等他了,然后你先回家了。等到5点的时候你去看了一次,没来呢,又回家。等到6点的时候去东门见到了快递,收到包裹。非阻塞等待 ## 同步阻塞 你在超市买了个锅,超市送货上门,你就一直等着锅送上门然后开始做饭。同步阻塞 然后你网购了一件裙子,从苏州发货,你还在干等着裙子送上门。这个人脑子有问题,这个同步阻塞等待用的不太好

同步非阻塞

你在超市买了个锅,超市送货上门。然后你在家等着也无聊,打扫了一下家里卫生,洗洗衣服。然后锅到了开始做饭

异步阻塞

闺蜜过生日你网购买了个衣服当礼物,从苏州发货,下了个订单之后就管不了了,该干啥干啥。快递送到了,你带着去给闺蜜过生日.

异步非阻塞

你网购买了个衣服,从苏州发货,下了个订单之后就不管了,该干啥干啥。两天之后快递给你打电话去取,然后完成本次购物

IO模型

阻塞

当去读文件时,一直等到我们需要的数据放到指定的内存中。常见的是recvfrom,一直等到有可用的数据搬移到我们的内存中后才继续往下走

  recvfrom(fd, buff)  没有数据时将会在内核中睡眠,接收到数据后才返回
  parse(buff)

这是最常用的模型,而当有多个阻塞事件时,通常使用多线程配合,每个线程阻塞等待一个事件

非阻塞

当去读文件时,如果没有数据呢就直接返回,有数据一直等到我们需要的数据放到指定的内存中。

  while read(fd,buff) == EAGAIN||EWOULDBLOCK
    sleep(1)

在实际使用中,使用非阻塞方式时,如果没有数据就返回的时候,通常是过一段时间再来询问的.

单独的非阻塞没有什么意义

多路复用

我们需要监视多个文件时,除了使用多线程阻塞的方式之外,我们还可以选择多路复用,将我们关心的文件描述符告诉内核,内核来告诉我们,通常有select和epoll两个api select

while(1) {
    FD_ZERO(&rfds);
    FD_SET(fd, &rfds);
    int retval = select(fd + 1, &rfds, NULL, NULL, NULL);
    if(FD_ISSET(fd, &rfds)) {

    }
}   

1.清空文件描述符位图
2.将自己关心的文件描述符位图置位
3.将文件描述符位图告诉内核,内核监视这些文件的变化并让出CPU
4.有文件变化时,遍历文件描述符位图,处理文件
5.重新开始步骤1

epoll

epollfd = epoll_create();

ev.events = EPOLLIN;
ev.data.fd = sockfd;
epoll_ctl(epollfd, EPOLL_CTL_ADD, sockfd, &ev)  

for (;;) {
    nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1);

    for (n = 0; n < nfds; ++n) {
        if (events[n].data.fd == sockfd) {

        }
    }
}

1.初始化epoll事件
2.将关心的文件描述符添加到epoll事件中
3.告诉内核epoll事件,然后它监视这些变化
4.将有变动的文件告诉你,遍历这些文件,处理文件
5.重新开始步骤4

select和epoll相比,select每次都需要重新设置文件描述符位图;当有文件可用时,需要遍历所有的文件描述符; 但是只有少许文件时,select的这些开销是可以忽略不计的

异步IO

进程发起read操作之后,立刻就可以开始去做其它的事。而另一方面,从kernel的角度,当它受到一个asynchronous read之后 ,首先它会立刻返回,所以不会对用户进程产生任何block。然后,kernel会等待数据准备完成,然后将数据拷贝到用户内存,当这 一切都完成之后,kernel会给用户进程发送一个signal,告诉它read操作完成了。
这个模型工作机制是:告诉内核启动某个操作,并让内核在整个操作(包括第二阶段,即将数据从内核拷贝到进程缓冲区中)完成 后通知我们。
这种模型和前一种模型区别在于:信号驱动I/O是由内核通知我们何时可以启动一个I/O操作,而异步I/O模型是由内核通知我们I/O操 作何时完成。