python 3.x 异步I/O(并发,并行,同步,异步,阻塞)概念篇
异步I/O(并发,并行,同步,异步,阻塞)概念篇
- 协成和异步IO
- 相互关联又相互独立
-
并发:
- 指一个时间段内(不是时间点),有几个程序在同一个cpu上运行,但是任意时刻只有一个程序在cpu上运行.
- 详解:
- 一个cpu,同一个时间点只有可能执行一个程序,因为cpu的运转速度非常非常的高,1秒钟可以达到上亿次.
所以说对操作系统来说,cpu虽然在同一个时间点上,只能够运行一个线程,但是在1秒之内或者说在一个时间段之内,
操作系统是可以做到完成多个应用程序的切换,所以说对人类的时钟来说,1秒钟能干一件事,就觉得效率非常非常的高,但是对于计算机来说,1秒实际上可以运行上亿次的.
比如在1秒之内cpu如果切换了100个进程,就可以认为cpu并发是100.
在人类看来cpu可以运行N多程序,但实际上对计算机来说,计算机在某一个时刻只能运行一个程序,只是对人类来说在一个时间段之内,计算机好像是对多个进程执行的程序,这就是并发.
因为感觉很多程序同时运行.但实际上在某一时刻只有一个程序在运行
- 一个cpu,同一个时间点只有可能执行一个程序,因为cpu的运转速度非常非常的高,1秒钟可以达到上亿次.
-
并行:
- 指任意时刻点上,有多个程序同时运行在多个cpu上
- 详解:
- 多个cpu,每个cpu是独立运行自己的程序,他们之间互不干扰,这种情况下他们实际是并行在运行的.
并行的数量与cpu的数量是一致的,比如cpu是4核,在并行的时候最多能达到4.
- 多个cpu,每个cpu是独立运行自己的程序,他们之间互不干扰,这种情况下他们实际是并行在运行的.
-
通过案例来说明并发与并行的区别:
- 问题:喝茶
- 当前情况:开水没有;水壶要洗;茶壶茶杯要洗;火生好了;茶叶也有了.怎么办???
- 时间分配:
- 洗水壶: 3 min
- 灌凉水: 1 min
- 洗茶壶: 3 min
- 洗茶杯: 3 min
- 拿茶叶: 1 min
- 泡 茶: 1 min
- 烧开水: 30 min
- 并发版:老张一个人来完成,但是老张需要尽快的完成这些操作,所以说让老张完成并发的操作,把老张当做一个cpu.
老张怎样尽快的完成泡茶呢???- 办法甲:(有并发)
- 洗好水壶,灌上凉水,放在火上; 3+1=4 min
- 在等水开的时间里,洗茶壶,洗茶杯,拿茶叶; 30 min
- 等水开了,泡茶喝. 1 min
- 总用时 35 min
- 办法乙:(没有并发)
- 先做好一些准备工作,洗水壶,洗茶壶,洗茶杯,拿茶叶; 3+3+3+1=10min
- 一切就绪,灌凉水,烧开水; 1+30=31min
- 坐等水开,泡茶. 1min
- 总用时 42 min
- 办法丙:(没有并发)
- 洗水壶,灌凉水,烧开水; 3+1+30=34min
- 水开了之后,急急忙忙洗茶壶,洗茶杯,拿茶叶,泡茶. 3+3+1+1+急急忙忙耽误的时间 > 8min
- 总用时 大于42min
- 办法甲:(有并发)
- 并行版:
- 老张cpu:洗水壶,灌凉水,烧开水; 3+1+30=34min
- 老李cpu:洗茶壶;
- 老谢cpu:洗茶杯;
- 老赵cpu:拿茶叶;
- 老钱cpu:泡 茶; 1min
- 总用时 小于35 min(假象)
-
综上:
- 并发用时比并行用时要长
-
涉及到IO操作的时候,才会涉及得到同步,异步,阻塞,非阻塞,在没有IO操作的时候是不需要考虑这四种情况.但是平时写的代码中几乎是不可能不涉及到IO操作的.几乎都会涉及到磁盘操作.
- 同步:(消息通信的一种机制,相当于发送一个消息给另外一个线程,或者说另外一个协成让他执行某些操作)
- 是指代码调用IO操作时,必须等待IO操作完成之后才返回的调用方式.
- 直接调用一个函数或者直接调用IO操作,等待函数执行完成之后再进行下一步操作.
- 异步:(消息通信的一种机制,相当于发送一个消息给另外一个线程,或者说另外一个协成让他执行某些操作)
- 是指代码调用IO操作时,不必等待IO操作完成就返回的调用方式.
- 多线程就是一个典型的异步操作.因为将某一个任务交给某一个线程的时候,立马就得到返回了.
- 线程池也是一样的,在submit()之后,就立马得到一个future.后面就会通过future拿到异步操作的结果.
- 多线程将函数交给某一个线程后,就可以立马在主线程里面执行剩余的逻辑.
- 阻塞:(函数调用的机制)
- 是指调用函数时候当前线程被挂起.
- 非阻塞:(函数调用的机制)
- 是指调用函数时候当前线程不会挂起,而是立即返回.
- 同步:(消息通信的一种机制,相当于发送一个消息给另外一个线程,或者说另外一个协成让他执行某些操作)
-
C10K问题和IO多路复用(select,poll,epoll)
- C10K问题:
- 是一个在1999年被提出来的技术挑战
- 在网络最开始的编程当中,用户非常的少,少到几乎不会涉及到用户的并发问题,但是随着网络的发展,越来越多的用户开始使用互联网,所以说在最初期的时候,遇到了一个并发的问题.
- 就是我们希望在一颗1GHz CPU,2G内存,1gbps网络环境下,让单台服务器同时为1万个客户端提供FTP服务.如何做???
- 多用户连接的方法,在socket章节说过当时处理用户的并发使用的是线程的操作threading.Thread完成的,线程的模式理解起来很简单,但是有问题,
一个线程只能处理一个socket,就意味着一个线程处理一个用户.在初期的服务器当中,一个计算机能开的线程不多,
所以说使用线程的模式几乎是不可能完成一个服务器服务上万个用户的.因为是不可能开启上万个线程的.
线程的模式最大的问题就是一个线程只能不停的去处理一个用户请求.
- 多用户连接的方法,在socket章节说过当时处理用户的并发使用的是线程的操作threading.Thread完成的,线程的模式理解起来很简单,但是有问题,
- 如何才能在一个简单的服务器上去处理多个用户的请求???
- 需要涉及到之前介绍的概念,阻塞,非阻塞,同步,异步以及IO多路复用.
- 开始介绍IO多路复用之前,先来介绍unix中的5种I/O模型.
- 阻塞式I/O
- 编程中用到的最多,也是最初级的一种,在学编程的时候几乎所有的I/O都使用的是阻塞式I/O.
- 在当前的编码当中阻塞式I/O也是用的非常的多
- 非阻塞式I/O
- 进一步介绍socket编程的时候,会接触到非阻塞式I/O
- I/O多路复用
- 信号驱动式I/O
- 现在使用的非常的少
- 异步I/O(POSIX的aio_系列函数以及windows下的iocp)
- 阻塞式I/O
- C10K问题:
-
阻塞式I/O
![]()
- 比如拿网络I/O当中的recvfrom()函数,就是从端口里面读取数据,这就是一个典型的阻塞式I/O案例
- 比如获取一个网页的返回,直接调用recvfrom()函数的话,recvfrom()就会一直阻塞,因为服务器不返回的话,recvfrom()就会一直阻塞.直到服务器返回数据.
- 之前在模拟爬虫的时候,实现socket http的案例,在这个里面有几个函数是非常典型的阻塞式I/O.
- connect()函数,
- socket连接的时候,connect()有三次握手.三次握手都会走网络协议.
- 如果connect()函数不返回的话,当前的线程会一直停在connect()中.大量的等待网络连接时间.
- send()函数
- recv()函数
- 如果服务器没有返回,将一直停在recv()函数中,等到网络数据的返回.
- 网络等待的时间远大于cpu操作的时间
- I/O的时间与cpu操作的时间相差的级别是非常非常的大,所以说在等待的过程中,浪费大量的cpu,
因为在阻塞式I/O当中cpu是属于空闲的,就意味着cpu在大量的等待,这样对cpu的利用率是非常低的.
在整个计算机中最宝贵的资源就是cpu资源.这就网络编程中阻塞式I/O给我们带来最不好的地方.
阻塞式I/O使用起来很简单,时间浪费很严重,就因为I/O阻塞了.
- I/O的时间与cpu操作的时间相差的级别是非常非常的大,所以说在等待的过程中,浪费大量的cpu,
- connect()函数,
-
为了解决这个问题,操作系统提供了对应的方法,就是在socket中设置setblocking(False)
-
非阻塞式I/O调用connect()方法的时候就会立马返回,不会等待连接好了之后才允许继续执行.
![]()
- 通过图解,可以知道,非阻塞式I/O直接调用recvfrom()函数,如果数据没有准备好,就会立马返回,中间是没有等待器的,不断的轮询调用recvfrom()函数,直到数据报准备好.
- 非阻塞式I/O带来不好的地方
- 试想一下,connect()方法调用后立马返回,虽然connect()调用后立马返回,但是并不代表整个网络三次握手就完成了.因为三次握手是需要时间过程的,这个时候调用send()函数的时候,会抛异常的(因为三次握手没有完成).
如果网络的连接没有建立好,就去调用send()函数,就会抛异常的.所以在调用send()函数之前,需要不停的询问连接是否建立好.虽然connect()方法没有阻塞(因为设置setblocking(False)),
但是需要进行后续的操作,就必须确保connect()已经将连接建立好,所以需要一个while循环不停的检查状态(连接是否建立好). - while循环时非常消耗cpu的.
- 因为不断的循环询问连接是否建立成功,while循环耗cpu.这种情况下消耗的时间和同步是差不多的,同时还消耗cpu.所以看来非阻塞式I/O不如阻塞式I/O,最起码阻塞式I/O不会消耗cpu
- 阻塞不会消耗cpu的
- 有一种情况,connect()的下一行代码,不依赖于connect()是否连接成功.
- 比如在connect()下面的代码是做计算任务或者再次发起其他的连接请求.这个时候是不依赖于connect()的连接是否连接好的.
所以这种情况下,非阻塞式I/O的优势就很明显了.可以做其他的事情,如果使用阻塞式I/O就做不了其他的事.
- 比如在connect()下面的代码是做计算任务或者再次发起其他的连接请求.这个时候是不依赖于connect()的连接是否连接好的.
- 试想一下,connect()方法调用后立马返回,虽然connect()调用后立马返回,但是并不代表整个网络三次握手就完成了.因为三次握手是需要时间过程的,这个时候调用send()函数的时候,会抛异常的(因为三次握手没有完成).
- 阻塞式I/O与非阻塞式I/O没有可比性,因为他俩在不同的业务逻辑上,各有各的优势.
- 将数据从内核复制到用户空间:(有图)
- 整个内存,比如有8G的内存,在内存中有操作系统比如Linux,和应用程序.操作系统为了安全管理内存,因为操作系统需要使用内存,应用程序也需要使用内存.
为了安全性以及权限问题,操作系统会拿出第一地址的一个G内存把它保护起来,供操作系统使用.然后整个应用程序申请的内存是在剩下7G中,
所有应用程序不能访问第一地址的内存.这个是为了安全考虑.操作系统没有访问. - 比如调用recvfrom()函数时,recvfrom()函数只有操作系统底层支持,我们的应用程序才能调用recvfrom()函数.应用程序调用recvfrom()函数,
实际上会深入操作系统,让操作系统帮我们做事.实际上是调用操作系统的函数.操作系统再去请求网络Internet,网络返回数据时,
第一步返回到内核空间/缓存(第一地址的内存).是因为操作系统发起请求.当应用程序想使用网络返回的数据时,将数据从内核空间拷贝到应用程序空间.
每一个应用程序都有一个自己的缓存,将数据从操作系统的地址拷贝应用程序的缓存地址里面.应用程序是访问不了内核的缓存.
- 整个内存,比如有8G的内存,在内存中有操作系统比如Linux,和应用程序.操作系统为了安全管理内存,因为操作系统需要使用内存,应用程序也需要使用内存.
- 非阻塞式I/O反复不停的去请求连接状态的问题,是一个耗cpu的过程.也是耗时的过程.
- 通以上阻塞式I/O和非阻塞式I/O产生疑问???
- 有没有一种机制可以让操作系统在将数据准备好之后,比如数据进入到操作系统的缓存以后,操作系统再来发一个消息告诉应用程序说数据准备好了.这就要使用IO多路复用(select,poll,epoll)
-
IO多路复用:
- IO多路复用在现代编程应用非常广,也是目前高并发技术里面使用最广泛的一个点
- IO多路复用存在一个问题,就是在判断某一个socket准备好之后,去调用recvfrom()时,依然有一个时间(将数据从内核复制到用户空间的时间)还是省不了,但是将while循环监听的时间省略掉了
-
- 三种技术:select,poll,epoll
- select是最早的模型,是操作系统给我们提供一个方法.调用select方法之后,操作系统会返回哪些socket,文件句柄已经准备好了.
这样的话再去调用select方法,判断状态是否已经建立连接好(connect()).
- select是最早的模型,是操作系统给我们提供一个方法.调用select方法之后,操作系统会返回哪些socket,文件句柄已经准备好了.
- 有人还是会问select这种模式跟非阻塞式I/O中不停循环请求connect的状态,实际上感觉好像也没有什么区别???都是需要等待connect连接完成.
- select方法是一个阻塞的方法,如果操作系统里面没有一个socket或者文件句柄准备好了,select实际上会一直阻塞住的.
- 有的人会把select,poll,epoll归位异步非阻塞式I/O,这种说法并不准确.
- select本身是一个阻塞式的,但是select与非阻塞式I/O中的while循环有一个很大的区别,select可以监听多个文件句柄和socket,就是放一个list进来,
list中存放所有的socket,list中一但有一个socket发生变化,select就会返回,有了这一点,
最大的好处就是select可以同时监听多个socket的一个状态,而非阻塞式I/O的while循环只是监听一个socket的状态.
- select本身是一个阻塞式的,但是select与非阻塞式I/O中的while循环有一个很大的区别,select可以监听多个文件句柄和socket,就是放一个list进来,
- select监听多个socket的好处:
- 如果同时发起100个非阻塞式I/O connect请求,直切使用select去监听这100socket,这样的话,一但有一个状态发生变化,就可以立马处理他.
如果不使用select的话,是做不到多个socket中有一个状态发生变化,就可以立马处理他.
- 如果同时发起100个非阻塞式I/O connect请求,直切使用select去监听这100socket,这样的话,一但有一个状态发生变化,就可以立马处理他.
- IO多路复用的用途是非常的明显:
- 调用select之后,在socket或者文件句柄准备好的时候,就返回可读文件.这时立马可以做自己的业务逻辑,可以调用recvfrom(),然后去处理socket,通过图可以看出IO多路复用是没有轮询的.
- IO多路复用在现在的应用中运用非常广泛,也是目前高并发技术使用最广泛的一个点.
- 三种技术:select,poll,epoll
-
- 但是IO多路复用依然存在一个问题,在判断某一个socket准备好之后,去调用recvfrom()时,依然有一个时间,是将数据从内核复制到用户空间的时间,还是省不了,但是将轮循的时间省掉了.
将数据从内核复制到用户空间,好想还是有提升的空间.
-
信号驱动式I/O(忽略,用的少)
-
异步IO(真正意义上的异步IO,是以aio开头的)
- 现在能接触到的很多高并发框架,实际上都没有使用aio,实际在很大程度上使用的都是IO多路复用技术.因为IO多路复用技术很成熟而且比较稳定.
- 真正的aio实际上在应用程序中使用的并不多.因为在实际使用过程中发现aio比IO多路复用在性能提升并没有达到一个很明显的程度,所以说当前的情况之下,IO多路复用被广泛应用.
- 真正意义上的aio,在调用aio_read的时候,一直到最后,整个操作系统将数据从内核复制到用户空间之后,再给信号处理程序发起一个数据,少了一个拷贝数据的过程.是操作系统准备好后再发.
- aio的编码难度比IO多路复用的编码难度高的多.所以目前大部分成熟的框架都使用的是IO多路复用.
重点学习IO多路复用,因为大部分成熟的框架都使用的是IO多路复用
- select,poll,epoll
- select,poll,epoll都是IO多路复用的机制.
- IO多路复用就是通过一种机制,一个进程可以监视多个描述符,一旦某个描述符就绪(一般是可读就绪或者可写就绪),能够通知程序进行相应的读写操作.
- 但select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写(把数据从内核拷贝到用户空间),也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间
- select
- select函数监视的文件描述符分3类,分别是writefds,readfds和exceptfds.
- 调用后select函数会阻塞,直到有描述符就绪(有数据可读,可写,或者有except(异常)),或者超时(timeout指定等待时间,如果立即返回设为null即可),函数返回.
- 当select函数返回后,可以通过遍历fdset,来找到就绪的描述符.
- select目前几乎在所有的平台上支持,其良好跨平台支持也是它的一个优点.
- select的一个缺点在于单进程能够监视的文件描述符的数量存在最大限制,在Linux上一般为1024,可以通过修改宏定义甚至重新编译内核的方式提升这一限制,但这样也会造成效率的降低.
- 调用select()函数时,select会遍历所有的fds的,所以说select的效率比较低.
- poll
- 不同与select使用三个位图来表示三个fdset的方式,poll使用一个pollfd的指针实现.
- pollfd结构包含了要监视的event和发生的event,不再使用select"参数-值"传递的方式.
- 同时,pollfd并没有最大数量限制(但是数量过大后性能也是会下降).和select函数一样,poll返回后,需要轮询pollfd来获取就绪的描述符.
- 从上面来看,select和poll都需要在返回后,通过遍历文件描述符来获取已就绪的socket.
- 事实上,同时连接的大量客户端在某一时刻可能只有很少的处于就绪状态,因此随着监视的描述符数量的增长,其效率也会线性下降.
- poll最大的优点就是查询效率高,而且没有最大数量限制.
- 所以说poll与select对比,pollfd虽然没有最大数量限制,但是poll的查询效率的提升实际上并不大.
- epoll
- epoll是在Linux环境下支持的,在windows下是不支持的.
- epoll是在Linux2.6内核中提出的,是之前的select和poll的增强版本.相对于select和poll来说,epoll更加灵活,没有描述符限制.
- epoll使用一个文件描述符管理多个描述符,将用户关系的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的copy只需一次.
- epoll的查询使用了数据结构里面的一个性能很高的数据结构,就是红黑树.
- 红黑树效率高,本身是非常复杂的.
- 概念非常多,没必要把概念记住,只需要知道:
- select,poll,epoll之间的关系是一个不停的进化的一个过程.
- 平常使用高性能的服务器,比如unix实际上使用的就是epoll.
- epoll并不代表一定比select要好,他们的区别:
- 在并发高的情况下,(高并发还有一种情况,就是连接活跃度不是很高的情况下)epoll比select好.
- 在并发高,特别是网站或者web系统当中,这个实际上就是一个典型的连接活跃度并不高,因为用户在连接之后,很有可能随时都有可能关闭掉连接.
- 比如用户在请求一个网页之后,后期一直不访问这个页面.
- 并发性不高,同时连接很活跃这种情况下,select比epoll好.
- 连接活跃:
- 就是用户在建立连接之后,不会说建立一次连接就不再管了或者说建立了之后,会明确的告诉你,我会断掉.
- 在游戏开发中就是一个典型的用例.游戏一但建立连接好之后,断得时候是比较少的,会一直处于连接状态.
- 而且这个连接实际上整个发送数据很灵活的,这种情况下select比epoll好
- 连接活跃:
- 在并发高的情况下,(高并发还有一种情况,就是连接活跃度不是很高的情况下)epoll比select好.
- 虽然epoll在内部的优化上比select好,但是select在某些情况下是比epoll好的.
********
posted on 2019-05-07 12:08 jaydenjune 阅读(74) 评论(0) 收藏 举报


浙公网安备 33010602011771号