Python并发编程-事件驱动模型
一、事件驱动模型介绍
1、传统的编程模式
例如:线性模式大致流程
开始--->代码块A--->代码块B--->代码块C--->代码块D--->......--->结束
每一个代码块里是完成各种各样事情的代码,但编程者知道代码块A,B,C,D...的执行顺序,唯一能够改变这个流程的是数据。输入不同的数据,根据条件语句判断,流程或许就改为A--->C--->E...--->结束。每一次程序运行顺序或许都不同,但它的控制流程是由输入数据和你编写的程序决定的。如果你知道这个程序当前的运行状态(包括输入数据和程序本身),那你就知道接下来甚至一直到结束它的运行流程。
例如:事件驱动型程序模型大致流程
开始--->初始化--->等待
与上面传统编程模式不同,事件驱动程序在启动之后,就在那等待,等待什么呢?等待被事件触发。传统编程下也有“等待”的时候,比如在代码块D中,你定义了一个input(),需要用户输入数据。但这与下面的等待不同,传统编程的“等待”,比如input(),你作为程序编写者是知道或者强制用户输入某个东西的,或许是数字,或许是文件名称,如果用户输入错误,你还需要提醒他,并请他重新输入。事件驱动程序的等待则是完全不知道,也不强制用户输入或者干什么。只要某一事件发生,那程序就会做出相应的“反应”。这些事件包括:输入信息、鼠标、敲击键盘上某个键还有系统内部定时器触发。
2、事件驱动模型
通常,我们写服务器处理模型的程序时,有以下几种模型:
(1)每收到一个请求,创建一个新的进程,来处理该请求; (2)每收到一个请求,创建一个新的线程,来处理该请求; (3)每收到一个请求,放入一个事件列表,让主进程通过非阻塞I/O方式来处理请求
3、第三种就是协程、事件驱动的方式,一般普遍认为第(3)种方式是大多数网络服务器采用的方式
示例:
1 #事件驱动之鼠标点击事件注册2 3 <!DOCTYPE html>4 <html lang="en">5 <head>6 <meta charset="UTF-8">7 <title>Title</title>8 9 </head>
10 <body>
11
12 <p onclick="fun()">点我呀</p>
13
14
15 <script type="text/javascript">
16 function fun() {
17 alert('约吗?')
18 }
19 </script>
20 </body>
21
22 </html>
执行结果:
在UI编程中,常常要对鼠标点击进行相应,首先如何获得鼠标点击呢?
两种方式:
1、创建一个线程循环检测是否有鼠标点击
那么这个方式有以下几个缺点:
- CPU资源浪费,可能鼠标点击的频率非常小,但是扫描线程还是会一直循环检测,这会造成很多的CPU资源浪费;如果扫描鼠标点击的接口是阻塞的呢?
- 如果是堵塞的,又会出现下面这样的问题,如果我们不但要扫描鼠标点击,还要扫描键盘是否按下,由于扫描鼠标时被堵塞了,那么可能永远不会去扫描键盘;
- 如果一个循环需要扫描的设备非常多,这又会引来响应时间的问题;
所以,该方式是非常不好的。
2、事件驱动模型
目前大部分的UI编程都是事件驱动模型,如很多UI平台都会提供onClick()事件,这个事件就代表鼠标按下事件。事件驱动模型大体思路如下:
-
- 有一个事件(消息)队列;
- 鼠标按下时,往这个队列中增加一个点击事件(消息);
- 有个循环,不断从队列取出事件,根据不同的事件,调用不同的函数,如onClick()、onKeyDown()等;
- 事件(消息)一般都各自保存各自的处理函数指针,这样,每个消息都有独立的处理函数;
什么是事件驱动模型 ?
目前大部分的UI编程都是事件驱动模型,如很多UI平台都会提供onClick()事件,这个事件就代表鼠标按下事件。事件驱动模型大体思路如下:
-
- 有一个事件(消息)队列;
- 鼠标按下时,往这个队列中增加一个点击事件(消息);
- 有个循环,不断从队列取出事件,根据不同的事件,调用不同的函数,如onClick()、onKeyDown()等;
- 事件(消息)一般都各自保存各自的处理函数指针,这样,每个消息都有独立的处理函数;
事件驱动编程是一种编程范式,这里程序的执行流由外部事件来决定。它的特点是包含一个事件循环,当外部事件发生时使用回调机制来触发相应的处理。另外两种常见的编程范式是(单线程)同步以及多线程编程。
需知:每个cpu都有其一套可执行的专门指令集,如SPARC和Pentium,其实每个硬件之上都要有一个控制程序,cpu的指令集就是cpu的控制程序。
二、IO模型准备
在进行解释之前,首先要说明几个概念:
- 用户空间和内核空间
- 进程切换
- 进程的阻塞
- 文件描述符
- 缓存 I/O
1、用户空间和内核空间
例如:采用虚拟存储器,对于32bit操作系统,它的寻址空间(虚拟存储空间为4G,即2的32次方)。
操作系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也可以访问底层硬件的所有权限。
为了保证用户进程不能直接操作内核(kernel),保证内核的安全,操作系统将虚拟空间划分为两部分:一部分为内核空间,另一部分为用户空间。
那么操作系统是如何分配空间的?这里就会涉及到内核态和用户态的两种工作状态。
1G: 0 --->内核态
3G: 1 --->用户态
CPU的指令集,是通过0和1 决定你是用户态,还是内核态
计算机的两种工作状态: 内核态和用户态
cpu的两种工作状态:
现在的操作系统都是分时操作系统,分时的根源,来自于硬件层面操作系统内核占用的内存与应用程序占用的内存彼此之间隔离。cpu通过psw(程序状态寄存器)中的一个2进制位来控制cpu本身的工作状态,即内核态与用户态。
内核态:操作系统内核只能运作于cpu的内核态,这种状态意味着可以执行cpu所有的指令,可以执行cpu所有的指令,这也意味着对计算机硬件资源有着完全的控制权限,并且可以控制cpu工作状态由内核态转成用户态。
用户态:应用程序只能运作于cpu的用户态,这种状态意味着只能执行cpu所有的指令的一小部分(或者称为所有指令的一个子集),这一小部分指令对计算机的硬件资源没有访问权限(比如I/O),并且不能控制由用户态转成内核态。
2、进程切换
为了控制进程的执行,内核必须有能力挂起正在CPU上执行的进程,并恢复以前挂起的某个进程的执行,这种行为就被称为进程切换。
总结:进程切换是很消耗资源的。
3、进程的阻塞
正在执行的进程,由于期待的某些事件未发生,如请求系统资源失败、等待某种操作的完成、新数据尚未到达或无新工作做等,则由系统自动执行阻塞原语(Block),使自己由运行状态变为阻塞状态。可见,进程的阻塞是进程自身的一种主动行为,也因此只有处于运行态的进程(获得CPU),才可能将其转为阻塞状态。当进程进入阻塞状态,是不占用CPU资源的。
4、文件描述符fd
文件描述符(File descriptor)是计算机科学中的一个术语,是一个用于表述指向文件的引用的抽象化概念。
文件描述符在形式上是一个非负整数。实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。在程序设计中,一些涉及底层的程序编写往往会围绕着文件描述符展开。但是文件描述符这一概念往往只适用于UNIX、Linux这样的操作系统。
5、缓存 I/O
缓存 I/O 又被称作标准 I/O,大多数文件系统的默认 I/O 操作都是缓存 I/O。在 Linux 的缓存 I/O 机制中,操作系统会将 I/O 的数据缓存在文件系统的页缓存( page cache )中,也就是说,数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。用户空间没法直接访问内核空间的,内核态到用户态的数据拷贝。
缓存 I/O 的缺点:
数据在传输过程中需要在应用程序地址空间和内核进行多次数据拷贝操作,这些数据拷贝操作所带来的 CPU 以及内存开销是非常大的。
本文讨论的背景是Linux环境下的network IO。
IO发生时涉及的对象和步骤:
对于一个network IO (这里我们以read举例),它会涉及到两个系统对象,
1、一个是调用这个IO的process (or thread),
2、另一个就是系统内核(kernel)。
当一个read操作发生时,它会经历两个阶段:
1、等待数据准备 (Waiting for the data to be ready)
2、将数据从内核拷贝到进程中 (Copying the data from the kernel to the process)
记住这两点很重要,因为这些IO Model的区别就是在两个阶段上各有不同的情况。
常见的几种IO 模型:
- blocking IO (阻塞IO)
- nonblocking IO (非阻塞IO)
- IO multiplexing (IO多路复用)
- signal driven IO (信号驱动式IO)
- asynchronous IO (异步IO)
一、不常用的IO模型
1、信号驱动IO模型(Signal-driven IO)
使用信号,让内核在描述符就绪时发送SIGIO信号通知应用程序,称这种模型为信号驱动式I/O(signal-driven I/O)。
原理图:
首先开启套接字的信号驱动式I/O功能,并通过sigaction系统调用安装一个信号处理函数。该系统调用将立即返回,我们的进程继续工作,也就是说进程没有被阻塞。当数据报准备好读取时,内核就为该进程产生一个SIGIO信号。随后就可以在信号处理函数中调用recvfrom读取数据报,并通知主循环数据已经准备好待处理,也可以立即通知主循环,让它读取数据报。
无论如何处理SIGIO信号,这种模型的优势在于等待数据报到达期间进程不被阻塞。主循环可以继续执行 ,只要等到来自信号处理函数的通知:既可以是数据已准备好被处理,也可以是数据报已准备好被读取。
二、常用的四种IO模型:
1、 blocking IO(阻塞IO模型)
原理图:
示例:一收一发程序会进入死循环
server.py
1 #!/usr/bin/env python2 # -*- coding:utf-8 -*- 3 #Author: nulige4 5 import socket6 7 sk=socket.socket()8 9 sk.bind(("127.0.0.1",8080))
10
11 sk.listen(5)
12
13 while 1:
14 conn,addr=sk.accept()
15
16 while 1:
17 conn.send("hello client".encode("utf8"))
18 data=conn.recv(1024)
19 print(data.decode("utf8"))
client.py
1 #!/usr/bin/env python2 # -*- coding:utf-8 -*- 3 #Author: nulige4 5 import socket6 7 sk=socket.socket()8 9 sk.connect(("127.0.0.1",8080))
10
11 while 1:
12 data=sk.recv(1024)
13 print(data.decode("utf8"))
14 sk.send(b"hello server")
当用户进程调用了recvfrom这个系统调用,kernel就开始了IO的第一个阶段:准备数据。对于network io来说,很多时候数据在一开始还没有到达(比如,还没有收到一个完整的UDP包),这个时候kernel就要等待足够的数据到来。而在用户进程这边,整个进程会被阻塞。当kernel一直等到数据准备好了,它就会将数据从kernel中拷贝到用户内存,然后kernel返回结果,用户进程才解除block的状态,重新运行起来。
所以,blocking IO的特点就是在IO执行的两个阶段都被block了。
2、non-blocking IO(非阻塞IO)
原理图:
从图中可以看出,当用户进程发出read操作时,如果kernel中的数据还没有准备好,那么它并不会block用户进程,而是立刻返回一个error。从用户进程角度讲 ,它发起一个read操作后,并不需要等待,而是马上就得到了一个结果。用户进程判断结果是一个error时,它就知道数据还没有准备好,于是它可以再次发送read操作。一旦kernel中的数据准备好了,并且又再次收到了用户进程的system call,那么它马上就将数据拷贝到了用户内存,然后返回。
所以,用户进程其实是需要不断的主动询问kernel数据好了没有。
注意:
在网络IO时候,非阻塞IO也会进行recvform系统调用,检查数据是否准备好,与阻塞IO不一样,”非阻塞将大的整片时间的阻塞分成N多的小的阻塞, 所以进程不断地有机会 ‘被’ CPU光顾”。即每次recvform系统调用之间,cpu的权限还在进程手中,这段时间是可以做其他事情的,
也就是说非阻塞的recvform系统调用调用之后,进程并没有被阻塞,内核马上返回给进程,如果数据还没准备好,此时会返回一个error。进程在返回之后,可以干点别的事情,然后再发起recvform系统调用。重复上面的过程,循环往复的进行recvform系统调用。这个过程通常被称之为轮询。轮询检查内核数据,直到数据准备好,再拷贝数据到进程,进行数据处理。需要注意,拷贝数据整个过程,进程仍然是属于阻塞的状态。
示例:
服务端:
1 import time2 import socket3 sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM)4 sk.bind(('127.0.0.1',6667))5 sk.listen(5)6 sk.setblocking(False) #设置成非阻塞状态7 while True:8 try: 9 print ('waiting client connection .......')
10 connection,address = sk.accept() # 进程主动轮询
11 print("+++",address)
12 client_messge = connection.recv(1024)
13 print(str(client_messge,'utf8'))
14 connection.close()
15 except Exception as e: #捕捉错误
16 print (e)
17 time.sleep(4) #每4秒打印一个捕捉到的错误
客户端:
1 import time2 import socket3 sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM)4 5 while True:6 sk.connect(('127.0.0.1',6667))7 print("hello")8 sk.sendall(bytes("hello","utf8"))9 time.sleep(2)
10 break
缺点:
1、发送了太多系统调用数据
2、数据处理不及时
3、IO multiplexing(IO多路复用)
IO multiplexing这个词可能有点陌生,但是如果我说select,epoll,大概就都能明白了。有些地方也称这种IO方式为event driven IO。我们都知道,select/epoll的好处就在于单个process就可以同时处理多个网络连接的IO。它的基本原理就是select/epoll这个function会不断的轮询所负责的所有socket,当某个socket有数据到达了,就通知用户进程。
IO多路复用的三种方式:
1、select--->效率最低,但有最大描述符限制,在linux为1024。
2、poll ---->和select一样,但没有最大描述符限制。
3、epoll --->效率最高,没有最大描述符限制,支持水平触发与边缘触发。
IO多路复用的优势:同时可以监听多个连接,用的是单线程,利用空闲时间实现并发。
注意:
Linux系统: select、poll、epoll
Windows系统:select
Mac系统:select、poll
原理图:
当用户进程调用了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。
注意1:select函数返回结果中如果有文件可读了,那么进程就可以通过调用accept()或recv()来让kernel将位于内核中准备到的数据copy到用户区。
注意2: select的优势在于可以处理多个连接,不适用于单个连接
示例:
server.py
1 #server.py2 3 import socket4 import select5 sk=socket.socket()6 sk.bind(("127.0.0.1",9904))7 sk.listen(5)8 9 while True:
10 # sk.accept() #文件描述符
11 r,w,e=select.select([sk,],[],[],5) #输入列表,输出列表,错误列表,5: 是监听5秒
12 for i in r: #[sk,]
13 conn,add=i.accept()
14 print(conn)
15 print("hello")
16 print('>>>>>>')
client.py
1 import socket2 3 sk=socket.socket()4 5 sk.connect(("127.0.0.1",9904))6 7 while 1:8 inp=input(">>").strip()9 sk.send(inp.encode("utf8"))
10 data=sk.recv(1024)
11 print(data.decode("utf8"))
IO多路复用中的两种触发方式:
水平触发:如果文件描述符已经就绪可以非阻塞的执行IO操作了,此时会触发通知.允许在任意时刻重复检测IO的状态, 没有必要每次描述符就绪后尽可能多的执行IO.select,poll就属于水平触发。
边缘触发:如果文件描述符自上次状态改变后有新的IO活动到来,此时会触发通知.在收到一个IO事件通知后要尽可能 多的执行IO操作,因为如果在一次通知中没有执行完IO那么就需要等到下一次新的IO活动到来才能获取到就绪的描述 符.信号驱动式IO就属于边缘触发。
epoll:即可以采用水平触发,也可以采用边缘触发。
1、水平触发
只有高电平或低电平的时候才触发
1-----高电平---触发
0-----低电平---不触发
示例:
server服务端
1 #水平触发2 import socket3 import select4 sk=socket.socket()5 sk.bind(("127.0.0.1",9904))6 sk.listen(5)7 8 while True:9 r,w,e=select.select([sk,],[],[],5) #input输入列表,output输出列表,erron错误列表,5: 是监听5秒
10 for i in r: #[sk,]
11 print("hello")
12
13 print('>>>>>>')
client客户端
1 import socket2 3 sk=socket.socket()4 5 sk.connect(("127.0.0.1",9904))6 7 while 1:8 inp=input(">>").strip()9 sk.send(inp.encode("utf8"))
10 data=sk.recv(1024)
11 print(data.decode("utf8"))
2、边缘触发
1---------高电平--------触发
0---------低电平--------触发
IO多路复用优势:同时可以监听多个连接
示例:select可以监控多个对象
服务端
1 #优势2 import socket3 import select4 sk=socket.socket()5 sk.bind(("127.0.0.1",9904))6 sk.listen(5)7 inp=[sk,]8 9 while True:
10 r,w,e=select.select(inp,[],[],5) #[sk,conn],5是每隔几秒监听一次
11
12 for i in r: #[sk,]
13 conn,add=i.accept() #发送系统调用
14 print(conn)
15 print("hello")
16 inp.append(conn)
17 # conn.recv(1024)
18 print('>>>>>>')
客户端:
1 import socket2 3 sk=socket.socket()4 5 sk.connect(("127.0.0.1",9904))6 7 while 1:8 inp=input(">>").strip()9 sk.send(inp.encode("utf8"))
10 data=sk.recv(1024)
11 print(data.decode("utf8"))
多了一个判断,用select方式实现的并发
示例:实现并发聊天功能 (select+IO多路复用,实现并发)
服务端:
1 import socket2 import select3 sk=socket.socket()4 sk.bind(("127.0.0.1",8801))5 sk.listen(5)6 inputs=[sk,]7 while True: #监听sk和conn8 r,w,e=select.select(inputs,[],[],5) #conn发生变化,sk不变化就走else9 print(len(r))
10 #判断sk or conn 谁发生了变化
11 for obj in r:
12 if obj==sk:
13 conn,add=obj.accept()
14 print(conn)
15 inputs.append(conn)
16 else:
17 data_byte=obj.recv(1024)
18 print(str(data_byte,'utf8'))
19 inp=input('回答%s号客户>>>'%inputs.index(obj))
20 obj.sendall(bytes(inp,'utf8'))
21
22 print('>>',r)
客户端:
1 import socket
2 sk=socket.socket()
3 sk.connect(('127.0.0.1',8801))
4
5 while True:
6 inp=input(">>>>")
7 sk.sendall(bytes(inp,"utf8"))
8 data=sk.recv(1024)
9 print(str(data,'utf8'))
执行结果:
先运行服务端,再运行多个客户端,就可以聊天啦。(可以接收多个客户端消息)
#server
>> [<socket.socket fd=276, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8801)>]
1
hello
回答1号客户>>>word
>> [<socket.socket fd=344, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8801), raddr=('127.0.0.1', 54388)>]
1#clinet
>>>>hello
word
4、Asynchronous I/O(异步IO)
用户进程发起read操作之后,立刻就可以开始去做其它的事。而另一方面,从kernel的角度,当它受到一个asynchronous read之后,首先它会立刻返回,所以不会对用户进程产生任何block。然后,kernel会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,kernel会给用户进程发送一个signal,告诉它read操作完成了。
异步最大特点:全程无阻塞
synchronous IO(同步IO)和asynchronous IO(异步IO)的区别:
- A synchronous I/O operation causes the requesting process to be blocked until that I/O operationcompletes;
- An asynchronous I/O operation does not cause the requesting process to be blocked;
两者的区别就在于synchronous IO做”IO operation”的时候会将process阻塞。(有一丁点阻塞,都是同步IO)按照这个定义,之前所述的blocking IO,non-blocking IO,IO multiplexing都属于synchronous IO(同步IO)。
同步IO:包括 blocking IO、non-blocking、select、poll、epoll(故:epool只是伪异步而已)(有阻塞)
异步IO:包括:asynchronous (无阻塞)
五种IO模型比较:
经过上面的介绍,会发现non-blocking IO和asynchronous IO的区别还是很明显的。在non-blocking IO中,虽然进程大部分时间都不会被block,但是它仍然要求进程去主动的check,并且当数据准备完成以后,也需要进程主动的再次调用recvfrom来将数据拷贝到用户内存。而asynchronous IO则完全不同。它就像是用户进程将整个IO操作交给了他人(kernel)完成,然后他人做完后发信号通知。在此期间,用户进程不需要去检查IO操作的状态,也不需要主动的去拷贝数据。
5、selectors模块应用
python封装好的模块:selectors
selectors模块: 会选择一个最优的操作系统实现方式
示例:
select_module.py
1 import selectors2 import socket3 4 sel = selectors.DefaultSelector()5 6 def accept(sock, mask):7 conn, addr = sock.accept() # Should be ready8 print('accepted', conn, 'from', addr)9 conn.setblocking(False) #设置成非阻塞
10 sel.register(conn, selectors.EVENT_READ, read) #conn绑定的是read
11
12 def read(conn, mask):
13 try:
14 data = conn.recv(1000) # Should be ready
15 if not data:
16 raise Exception
17 print('echoing', repr(data), 'to', conn)
18 conn.send(data) # Hope it won't block
19 except Exception as e:
20 print('closing', conn)
21 sel.unregister(conn) #解除注册
22 conn.close()
23
24 sock = socket.socket()
25 sock.bind(('localhost', 8090))
26 sock.listen(100)
27 sock.setblocking(False)
28 #注册
29 sel.register(sock, selectors.EVENT_READ, accept)
30 print("server....")
31
32 while True:
33 events = sel.select() #监听[sock,conn1,conn2]
34 print("events",events)
35 #拿到2个元素,一个key,一个mask
36 for key, mask in events:
37 # print("key",key)
38 # print("mask",mask)
39 callback = key.data #绑定的是read函数
40 # print("callback",callback)
41 callback(key.fileobj, mask) #key.fileobj=sock,conn1,conn2
client.py
1 import socket2 3 sk=socket.socket()4 5 sk.connect(("127.0.0.1",8090))6 while 1:7 inp=input(">>>")8 sk.send(inp.encode("utf8")) #发送内容9 data=sk.recv(1024) #接收信息
10 print(data.decode("utf8")) #打印出来
执行结果:
先运行select_module.py,再运行clinet.py
#serverserver....
events [(SelectorKey(fileobj=<socket.socket fd=312, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8090)>, fd=312, events=1, data=<function accept at 0x01512F60>), 1)]
accepted <socket.socket fd=376, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8090), raddr=('127.0.0.1', 57638)> from ('127.0.0.1', 57638)
events [(SelectorKey(fileobj=<socket.socket fd=376, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8090), raddr=('127.0.0.1', 57638)>, fd=376, events=1, data=<function read at 0x015C26A8>), 1)]
echoing b'hello' to <socket.socket fd=376, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8090), raddr=('127.0.0.1', 57638)>
events [(SelectorKey(fileobj=<socket.socket fd=312, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8090)>, fd=312, events=1, data=<function accept at 0x01512F60>), 1)]
accepted <socket.socket fd=324, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8090), raddr=('127.0.0.1', 57675)> from ('127.0.0.1', 57675)
events [(SelectorKey(fileobj=<socket.socket fd=324, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8090), raddr=('127.0.0.1', 57675)>, fd=324, events=1, data=<function read at 0x015C26A8>), 1)]
echoing b'uuuu' to <socket.socket fd=324, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8090), raddr=('127.0.0.1', 57675)>
events [(SelectorKey(fileobj=<socket.socket fd=324, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8090), raddr=('127.0.0.1', 57675)>, fd=324, events=1, data=<function read at 0x015C26A8>), 1)]
closing <socket.socket fd=324, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8090), raddr=('127.0.0.1', 57675)>
events [(SelectorKey(fileobj=<socket.socket fd=312, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8090)>, fd=312, events=1, data=<function accept at 0x01512F60>), 1)]
accepted <socket.socket fd=324, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8090), raddr=('127.0.0.1', 57876)> from ('127.0.0.1', 57876)
events [(SelectorKey(fileobj=<socket.socket fd=324, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8090), raddr=('127.0.0.1', 57876)>, fd=324, events=1, data=<function read at 0x015C26A8>), 1)]
echoing b'welcome' to <socket.socket fd=324, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8090), raddr=('127.0.0.1', 57876)>#clinet (启动两个client)
>>>hello
hello>>>welcome
welcome
6、I/O多路复用的应用场景
(1)当客户处理多个描述字时(一般是交互式输入和网络套接口),必须使用I/O复用。
(2)当一个客户同时处理多个套接口时,而这种情况是可能的,但很少出现。
(3)如果一个TCP服务器既要处理监听套接口,又要处理已连接套接口,一般也要用到I/O复用。
(4)如果一个服务器即要处理TCP,又要处理UDP,一般要使用I/O复用。
(5)如果一个服务器要处理多个服务或多个协议,一般要使用I/O复用。
'''与多进程和多线程技术相比,I/O多路复用技术的最大优势是系统开销小,系统不必创建进程/线程,也不必维护这些进程/线程,从而大大减小了系统的开销。'''
最后,再举几个不是很恰当的例子来说明这四个IO Model:
有A,B,C,D四个人在钓鱼:
A用的是最老式的鱼竿,所以呢,得一直守着,等到鱼上钩了再拉杆;【阻塞】
B的鱼竿有个功能,能够显示是否有鱼上钩(这个显示功能一直去判断鱼是否上钩),所以呢,B就和旁边的MM聊天,隔会再看看有没有鱼上钩,有的话就迅速拉杆;【非阻塞】
C用的鱼竿和B差不多,但他想了一个好办法,就是同时放好几根鱼竿,然后守在旁边,一旦有显示说鱼上钩了,它就将对应的鱼竿拉起来;【同步】
D是个有钱人,干脆雇了一个人帮他钓鱼,一旦那个人把鱼钓上来了,就给D发个短信(消息回掉机制,主动告知)。【异步】
相关文章:

Python并发编程-事件驱动模型
一、事件驱动模型介绍 1、传统的编程模式 例如:线性模式大致流程 开始--->代码块A--->代码块B--->代码块C--->代码块D--->......---&…...

构建系统发育树简述
1. 要点 系统发育树代表了关于一组生物之间的进化关系的假设。可以使用物种或其他群体的形态学(体型)、生化、行为或分子特征来构建系统发育树。在构建树时,我们根据共享的派生特征(不同于该组祖先的特征)将物种组织成…...

这款 Python 调试神器推荐收藏
大家好,对于每个程序开发者来说,调试几乎是必备技能。 代码写到一半卡住了,不知道这个函数执行完的返回结果是怎样的?调试一下看看 代码运行到一半报错了,什么情况?怎么跟预期的不一样?调试一…...

金三银四吃透这份微服务笔记,面试保准涨10K+
很多人对于微服务技术也都有着一些疑虑,比如: 微服务这技术虽然面试的时候总有人提,但作为一个开发,是不是和我关系不大?那不都是架构师的事吗?微服务不都是大厂在玩吗?我们这个业务体量用得着…...

构建matter over Thread的演示系统-efr32
文章目录1. 简介2. 构建测试系统2.1设置 Matter Hub(Raspberry Pi)2.2 烧录Open Thread RCP固件2.3 烧录待测试的matter设备3. 配网和测试:3.1 使用mattertool建立Thread网络3.2 使用mattertool配置设备入网3.3 使用mattertool控制matter设备3.4 查看节点的Node ID等…...

【一天一门编程语言】Matlab 语言程序设计极简教程
Matlab 语言程序设计极简教程 用 markdown 格式输出答案。 不少于3000字。细分到2级目录。 目录 Matlab 语言程序设计极简教程 简介Matlab 工作空间Matlab 基本数据类型Matlab 语句和表达式Matlab 函数和程序Matlab 图形界面程序设计Matlab 应用实例 简介 Matlab是一种编…...

看似平平无奇的00后,居然一跃上岸字节,表示真的卷不过......
又到了一年一度的求职旺季金!三!银!四!在找工作的时候都必须要经历面试这个环节。在这里我想分享一下自己上岸字节的面试经验,过程还挺曲折的,但是还好成功上岸了。大家可以参考一下! 0821测评 …...

BZOJ2142 礼物
题目描述 一年一度的圣诞节快要来到了。每年的圣诞节小E都会收到许多礼物,当然他也会送出许多礼物。不同的人物在小E 心目中的重要性不同,在小E心中分量越重的人,收到的礼物会越多。小E从商店中购买了n件礼物,打算送给m个人 &…...

MySQL高级第一讲
目录 一、MySQL高级01 1.1 索引 1.1.1 索引概述 1.1.2 索引特点 1.1.3 索引结构 1.1.4 BTREE结构(B树) 1.1.5 BTREE结构(B树) 1.1.6 索引分类 1.1.7 索引语法 1.1.8 索引设计原则 1.2 视图 1.2.1 视图概述 1.2.2 创建或修改视图 1.3 存储过程和函数 1.3.1 存储过…...

前端面试常用内容——基础积累
1.清除浮动的方式有哪些? 高度塌陷:当所有的子元素浮动的时候,且父元素没有设置高度,这时候父元素就会产生高度塌陷。 清除浮动的方式: 1.1 给父元素单独定义高度 优点: 快速简单,代码少 缺…...

跟着《代码随想录》刷题(三)——哈希表
3.1 哈希表理论基础 哈希表理论基础 3.2 有效的字母异位词 242.有效的字母异位词 C bool isAnagram(char * s, char * t){int array[26] {0};int i 0;while (s[i]) {// 并不需要记住字符的ASCII码,只需要求出一个相对数值就可以了array[s[i] - a];i;}i 0;whi…...

HTML - 扫盲
文章目录1. 前言2. HTML2.1 下载 vscode3 HTML 常见标签3.1 注释标签3.2 标题标签3.3 段落标签3.4 换行标签3.5 格式化标签1. 加粗2. 倾斜3. 下划线3.6 图片标签3.7 超链接标签3.8 表格标签3.9 列表标签4. 表单标签4.1 from 标签4.2 input 标签4.3 select 标签4.4 textarea标签…...

【系统分析师之路】2022上案例分析历年真题
【系统分析师之路】2022上案例分析历年真题 【系统分析师之路】2022上案例分析历年真题【系统分析师之路】2022上案例分析历年真题2022上案例分析历年真题第一题(25分)2022上案例分析历年真题第二题(25分)2022上案例分析历年真题第…...

Python编程规范
Python编程规范 当今Python编程社区有许多关于编程规范的约定和惯例。以下是一些常见的Python编程规范: 1.使用有意义的命名 使用有意义的命名可以使代码更加清晰、易读、易维护。变量、函数、类和模块的命名应该能够明确传达其用途,而不是使用无意义…...

【Java】Spring Boot项目的创建和使用
文章目录SpringBoot的创建和使用1. 什么是Spring Boot?为什么要学Spring Boot?2. Spring Boot项目的优点3. Spring Boot 项目的创建3.1 使用idea创建3.2 接下来创建Spring Boot项目4. 项目目录介绍和运行4.1 运行项目4.2 输出内容5. 总结SpringBoot的创建…...

Malware Dev 00 - Rust vs C++ 初探
写在最前 如果你是信息安全爱好者,如果你想考一些证书来提升自己的能力,那么欢迎大家来我的 Discord 频道 Northern Bay。邀请链接在这里: https://discord.gg/9XvvuFq9Wb我会提供备考过程中尽可能多的帮助,并分享学习和实践过程…...

JavaScript HTML DOM 事件
文章目录JavaScript HTML DOM 事件对事件做出反应HTML 事件属性使用 HTML DOM 来分配事件onload 和 onunload 事件onchange 事件onmouseover 和 onmouseout 事件onmousedown、onmouseup 以及 onclick 事件JavaScript HTML DOM 事件 HTML DOM 使 JavaScript 有能力对 HTML 事件做…...

推荐算法——NCF知识总结代码实现
NCF知识总结代码实现1. NeuralCF 模型的结构1.1 回顾CF和MF1.2 NCF 模型结构1.3 NeuralCF 模型的扩展---双塔模型2. NCF代码实现2.1 tensorflow2.2 pytorchNeuralCF:如何用深度学习改造协同过滤? 随着技术的发展,协同过滤相比深度学习模型的…...

redis(4)String字符串
前言 Redis中有5大数据类型,分别是字符串String、列表List、集合Set、哈希Hash、有序集合Zset,本篇介绍Redis的字符串String Redis字符串 String是Redis最基本的类型,你可以理解成与Memcached一模一样的类型,一个key对应一个value…...

session一致性问题
在http访问请求中,web服务器会自动为同一个浏览器的访问用户自动创建唯一的session,提供数据存储功能。最常见的,会把用户的登录信息、用户信息存储在session中,以保持登录状态。只要用户不重启浏览器,每次http短连接请…...

上岸16K,薪资翻倍,在华为外包做测试是一种什么样的体验····
现在回过头看当初的决定,还是正确的,自己转行成功,现在进入了华为外包测试岗,脱离了工厂生活,薪资也翻了一倍不止。 我17年毕业于一个普通二本学校,电子信息工程学院,是一个很不出名的小本科。…...

django项目中如何添加自定义的django command
项目目录 1.我们自己建立的application叫做app,首先在这个app目录下,我们需要新建management目录,这个目录里应该包括:__ init__.py(内容为空,用于打包)和commands目录,然后在comma…...

【算法基础】哈希表⭐⭐⭐
一、哈希表 散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。 给定表M,存在函数f(key),对任意…...

基于SpringMVC、Spring、MyBatis开发的校园点餐系统
文章目录 项目介绍主要功能截图:后台登录用户管理商品管理评论管理订单管理角色管理咨询管理前台前台首页我的订单商品详情支付方式选择支付成功页面部分代码展示设计总结项目获取方式🍅 作者主页:Java韩立 🍅 简介:Java领域优质创作者🏆、 简历模板、学习资料、面试题…...

LeetCode 热题 C++ 148. 排序链表 152. 乘积最大子数组 160. 相交链表
力扣148 给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表 。 示例 1: 输入:head [4,2,1,3] 输出:[1,2,3,4]示例 2: 输入:head [-1,5,3,4,0] 输出:[-1,0,3,4,5]示例 3&#x…...

JavaScript 基础【快速掌握知识点】
目录 为什么要学JavaScript? 什么是JavaScript 特点: 组成: JavaScript的基本结构 基本结构 内部引用 外部引用 console对象进行输出 JavaScript核心语法 1、变量声明 2、数据类型 3、运算符 4、条件语句 5、循环语句 6、数组 7…...

基于Frenet优化轨迹的⾃动驾驶动作规划⽅法
动作规划(Motion Control)在⾃动驾驶汽⻋规划模块的最底层,它负责根据当前配置和⽬标配置⽣成⼀序列的动作,本⽂介绍⼀种基于Frenet坐标系的优化轨迹动作规划⽅法,该⽅法在⾼速情况下的ACC辅助驾驶和⽆⼈驾驶都具有较强…...

Spring(入门)
1. 什么是spring,它能够做什么?2. 什么是控制反转(或依赖注入)3. AOP的关键概念4. 示例 4.1 创建工程4.2 pom文件4.3 spring配置文件4.4 示例代码 4.4.1 示例14.4.2 示例2 (abstract,parent示例)4.4.3 使用有参数构造方法创建jav…...

2023-02-25力扣每日一题
链接: https://leetcode.cn/problems/minimum-swaps-to-make-strings-equal/ 题意: 给定字符串s1,s2,仅由x,y组成 每次可以在两边各挑一个字符交换 求让s1等于s2的最小步骤 解: 1000啊1000,双指针贪一下就过了 …...

如何外网登录管理云通信短信网关平台?——快解析映射方案
云通信(Cloud Communications )是基于云计算商业模式应用的通信平台服务,简单易用,满足企业一键群发场景,支持多种语言SDK和API 接入。各个通信平台软件都集中在云端,且互通兼容,用户只要登录云通信平台,不…...