并发编程(四)——IO模型

1.为什么用IO模型

  1.1多进程,多线程以及协程都有弊端

    (1)单多进程:

        能够有效利用多核优势,但是开启进程需要分配内存空间,占用内存,因此能够开启的进程有限,用进程池加以控制,因而并发量有限

    (2)单多线程:

        一个进程开启多个线程,线程并发量有限,同一时间只有一个线程占有CPU

    (3)单线程实现并发:

        协程:仅仅遇到IO阻塞时,进行切换切换的同时,不能实现并发

2.什么是IO模型

  2.1 同步异步以及阻塞、非阻塞

    同步异步:是提交任务后,是否停下来等待结果

      同步:提交任务后,等到任务执行完,返回结果,才继续下一步任务(提交任务或者其他)

      异步:提交任务后,不等待任务执行完,继续执行下一步(提交任务或者其他)

    阻塞和非阻塞:

      阻塞:遇到IO操作,程序停下来等待IO执行结果

      非阻塞:遇到IO操作,不等待,继续执行

  2.2:基于网络,我们常见有4种IO模型

每一次IO都要经历两个阶段即

  第一阶段:waiting data 阶段,从不知哪里发来的数据到OS内存。准备阶段,应用程序不占用CPU,比较浪费,主要是针对这个阶段进行的优化

  第二阶段:copy data 阶段:从系统内存把自己应用程序的数据copy到进程。此阶段耗时相当短暂

    (1)阻塞IO

      阻塞IO,就是在IO执行的两个阶段,即waiting data 和copy data 阶段都阻塞

服务端:

 1 from socket import *
 2 server = socket()
 3 server.bind(('127.0.0.1',8080))
 4 server.listen(5)
 5 
 6 conn,addr = server.accept()#阻塞的
 7 while True:
 8     data = conn.recv(1024)#阻塞的
 9     if not data:break#linux 系统断开连接,接收空
10     conn.send(data.upper())#阻塞的,本质是阻塞,只是 发送端只关心将应用程序数据copy到内核

客户端:

1 from socket import *
2 client = socket()
3 client.connect(('127.0.0.1',8080))
4 while True:
5     msg = input('>>>').strip()
6     if not msg:continue
7     client.send(bytes(msg,encoding='utf-8'))
8     data = client.recv(1024)
9     print(data.decode('utf-8'))

 

    (2)非阻塞IO:优化waiting data 阶段,即:准备数据阶段不阻塞。

        方式:通过socket的方法,设置socket的接口为非阻塞

 1 客户端:
 2 from socket import *
 3 client = socket()
 4 client.connect(('127.0.0.1',8080))
 5 while True:
 6     msg = input('>>>').strip()
 7     client.send(bytes(msg,encoding='utf-8'))
 8     data = client.recv(1024)
 9     print(data.decode('utf-8'))
10 
11 服务端:
12 from socket import *
13 server = socket()
14 server.bind(('127.0.0.1',8080))
15 server.listen(5)
16 server.setblocking(False)#设置socket的接口为非阻塞
17 conn_l = []
18 del_l = []
19 while True:#大幅度提高CPU的占用率,计算太多
20     try:
21         conn, addr = server.accept()
22         conn_l.append(conn)
23     except BlockingIOError:
24         print('没有数据')
25         for conn in conn_l:#遍历连接的列表,是否有通信过来
26             try:
27                 data = conn.recv(1024)
28                 if not data:
29                     del_l.append(conn)  # 针对Linux系统,断开连接后,发送空
30                     continue
31                 conn.send(data.upper())
32             except BlockingIOError:
33                 continue
34             except ConnectionRefusedError:#有连接断开时,抛出连接异常,关闭这个连接
35                 conn.close()
36                 del_l.append(conn)
37         for conn in del_l:
38             conn_l.remove(conn)
39 
40 
41 '''
42 执行原理:
43 在accept的位置:
44     有连接过来,进行连接,并把这个连接加入到连接的列表
45     没有连接过来,到则阻塞,抛出BlockingIOError,进行recv的操作
46 在recv的位置,检测每个连接是否有通信连接
47     有通信,接收
48     没有,抛出BlockingIOError(是一种异常或者一种信号,表明此时没有数据过来)
49 
50 
51 弊端:
52 1.不断询问是否有连接过来,大幅度提高CPU的占用率,计算太多
53 2.任何一个任务的响应延迟增大了
54 '''

 

 

    (3)IO多路复用

有三种模型:

  1.select模型,检测多个套接字的IO行为,循环问,从头到尾,效率低

    代替非阻塞IO的自己问询,由select模块进行检测是否有数据传来


  2.poll模型:比select检测的套接字多,工作原理和select一样


  3.epoll模型:哪个信息好了,返回信息。ngx检测多个套接字,绑定一个回调函数,基于Linux系统,Windows用不了

 

    (4)异步IO

异步IO执行原理:向操作系统发出一个阻塞的信号后,由操作系统进行等待,应用程序做自己的任务,等待操作系统等待数据传递到操作系统的内存后,向应用程序发布一个信号,应用程序再从操作系统copy数据,省略了等待数据和的过程

 

3.用IO模型的益处

 

posted @ 2017-10-18 20:32  wallisyan  阅读(49)  评论(0)    收藏  举报