学习笔记-同步(异步)和阻塞(非阻塞)的区别和联系

同步和异步、阻塞和非阻塞是操作系统的常见的概念,刚开始一直搞不懂,在看了n篇文章之后,终于有了一些感悟,现在就将我的理解贴出来与大家分享!!!

第一步:概念

(1)阻塞的概念:一个进程被阻塞的时候,从cpu的角度来说就要发生上下文切换,那么这个被阻塞的进程就不能被继续执行下去,把cpu让出来给别的进程。

(2)同步:在发出一个同步调用的时候,在没有得到结果之前就不会返回。那么对于发起调用的进程来说,在完成这个调用之前,它接下来的事情就不能进行,换句话说,

就是必须一件一件做,等前一件做完了才能做下一件事.

(3)异步:当一个异步过程调用发出后,调用者不能立刻得到结果。实际处理这个调用的部件在完成后,通过状态、通知、和回调函数来通知调用者。这里的重点是在调用

完成后会通过一种机制来告诉调用者。

从另一种角度来说,同步异步针对的是两个事情之间的关系,这种关系与拓扑结构中的先后关系类似类,如果两件事情之间存在拓扑关系,便是同步关系;如果没有时序先后的

关联或相互依赖,则是异步关系。而阻塞非阻塞针对的是发起任务的人(线程)在发起动作之后它所处的状态!

第二步:<<unix网络编程:卷一>>中涉及的I/O模型:

(1)阻塞式I/O模型:


我们可以看到这这种模型中,调用进程并没有继续执行下去,调用线程或进程被挂起让出cpu,等到对应的I/O就绪(想要的数据已经准备好的时候)并拷贝到用户空间之后

才唤醒进程(你可以继续往下执行了!!!)。所以一旦进程或线程以阻塞式的方式进行I/O的话,在某种程度上是同步I/O(即同步阻塞I/O)。

(2)非阻塞式I/O模型:



在非阻塞式的I/O模型中,调用者在调用recvform之后,函数立即返回-1,表示函数调用出错,出错的原因放在errno变量当中。这个时候调用者并没有阻塞,它既可以继续

调用reevform函数,也可以干其它的事。但我们调用recvfrom是为了获得数据,一般会利用循环while语句继续来检测fd是否准备就绪,但是这种做法十分浪费cpu资源,我们看

一个例子:

#include <stdio.h>
#include <sys/socket.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <time.h>
#include <strings.h>
#include <errno.h>

int main (int argc,char * argv[]){
    int servfd,clifd;
    struct sockaddr_in servaddr,cliaddr;

    if((servfd=socket(AF_INET,SOCK_STREAM,0))<0){
        printf("Create socket error!\n");
        return -1;
    }
    bzero(&servaddr,sizeof(servaddr));
    servaddr.sin_family=AF_INET;
    servaddr.sin_port=htons(5000);
    servaddr.sin_addr.s_addr=htonl(INADDR_ANY); //inet_addr("192.168.0.1");

    int option=1;
    setsockopt(servfd,SOL_SOCKET,SO_REUSEADDR,(char *)&option,sizeof(option));
    struct linger li;
    li.l_onoff=1;
    li.l_linger=1;
    setsockopt(servfd,SOL_SOCKET,SO_LINGER,(char *)&li,sizeof(li));
    if(bind(servfd,(struct sockaddr * )&servaddr,sizeof(servaddr))<0){
        perror("bind to port 5000 failure");
        return -1;
    }
    if(listen(servfd,10)<0){
        perror("listen error");
        return -1;
    } 

    int flags = fcntl(servfd, F_GETFL, 0);
    fcntl(servfd, F_SETFL, flags | O_NONBLOCK);

    while(1){
        socklen_t len;
        len=sizeof(cliaddr);
        clifd=accept(servfd,(struct sockaddr *)&cliaddr,&len);
        if(clifd < 0){
            if (errno==EAGAIN || errno == EWOULDBLOCK){
                usleep(10000);
                continue;
            }
            else{
                perror("call accept error");
                break;
            }
        }
        char szIp[17]; 
        bzero(szIp,17);
        inet_ntop(AF_INET,&cliaddr.sin_addr,szIp,16);
        printf("from client IP:%s,Port:%d\n",szIp,ntohs(cliaddr.sin_port));
        char buf[256];
        time_t t;
        time(&t);
        int datalen=sprintf(buf,"Server:%u\n",(unsigned int)t);
        send(clifd,buf,datalen,0);
        close(clifd);
    }
    close(servfd);
    return 0;
}
可以看到在服务器端用非阻塞的方式调用accept函数,在返回值是-1的时候,说明在内核的连接队列中没有已经完成的TCP连接,但是程序不会被阻塞,还是会继续执行下去,

所以就用while语句来轮询是否有已经建立的连接!!!!

(3)I/O多路复用模型:


当用户进程调用了select,那么整个进程会被block,而同时,kernel会“监视”所有select负责的socket,当任何一个socket中的数据准备好了,select就会返回。这个时候

用户进程再调用read操作,将数据从kernel拷贝到用户进程。这个图和blocking IO的图其实并没有太大的不同,事实上,还更差一些。因为这里需要使用两个system call 

(select 和 recvfrom),而blocking IO只调用了一个system call (recvfrom)。但是,用select的优势在于它可以同时处理多个connection。(多说一句。所以,如果处理的连接数

不是很高的话,使用select/epoll的web server不一定比使用multi-threading + blocking IO的web server性能更好,可能延迟还更大。select/epoll的优势并不是对于单个连接能

处理得更快,而是在于能处理更多的连接。)在IO multiplexing Model中,实际中,对于每一个socket,一般都设置成为non-blocking,但是,如上图所示,整个用户的

process其实是一直被block的。只不过process是被select这个函数block,而不是被socket IO给block。

(4)异步非阻塞I/O:

利用aio进行异步读取数据的一个范例:

#include <aio.h>
int main(){
  int fd, ret;
  struct aiocb my_aiocb;
  fd = open( "file.txt", O_RDONLY );
  if (fd < 0) perror("open");
  /* Zero out the aiocb structure (recommended) */
  bzero( (char *)&my_aiocb, sizeof(struct aiocb) );
  /* Allocate a data buffer for the aiocb request */
  my_aiocb.aio_buf = malloc(BUFSIZE+1);
  if (!my_aiocb.aio_buf) perror("malloc");
  /* Initialize the necessary fields in the aiocb */
  my_aiocb.aio_fildes = fd;
  my_aiocb.aio_nbytes = BUFSIZE;
  my_aiocb.aio_offset = 0;
  ret = aio_read( &my_aiocb );
  if (ret < 0) perror("aio_read");
  while ( aio_error( &my_aiocb ) == EINPROGRESS );
  if ((ret = aio_return( &my_aiocb )) > 0) {
    /* got ret bytes on the read */
  } else {
    /* read failed, consult errno */

  }
}
其中aio_read的返回值只有两种:aio_read 函数在请求进行排队之后会立即返回。如果执行成功,返回值就为 0;如果出现错误,返回值就为 -1,并设置 errno 的值。当请求

提交成功之后,我们就需要调用aio_errno来获取以下返回值:(1)EINPROGRESS,说明请求尚未完成(2)ECANCELLED,说明请求被应用程序取消了(3)-1,说明发生了错

误,具体错误原因可以查阅 errno一旦读取成功就调用aio_return来获取数据。可以看到这个模型中,如果数据没有准备好,我们不用继续调用aio_read,而是调用aio_errno来

获取请求状态。

下图是对以上模型的一个总结:



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值