Python中同步与异步编程
1)同步、异步
函数或方法被调用的时候,调用者是否得到最终的结果。
直接得到最终结果的结果,就是同步调用。(打饭模型,打饭不打好不走开,直到打饭给我后才离开)
不直接得到的最终的结果,就是异步调用。(打饭,不会一直等着,会时不时的过来看看,打完了把饭拿走,异步不保证多长时间打完了饭)
2)阻塞、非阻塞:
函数或方法调用的时候,是否立即返回。
立即返回就是非阻塞调用。
不立即返回就是阻塞调用。
3)区别:
同步、异步,与阻塞、非阻塞不相关。
同步、异步强调的是结果。
阻塞和非阻塞强调的是时间,是否等待。
同步与异步区别在于:调用者是否得到了想要的最终结果。
同步就是一直要执行到返回最终结果。
异步就是直接返回了,但是返回的不是最终的结果,调用者不能通过这种调用得到结果,还要通过被调用者,使用其他方式通知调用者,来取回最终结果。
阻塞与非阻塞的区别在于,调用者是否还能干其他的事情。
阻塞,调用者只能干等。
非阻塞,调用者可以先忙一会别的,不用一直等。
4)联系:
同步阻塞:调用者阻塞,直到等到拿到最终结果。(打饭模型,什么事情也不敢,就等着打饭,打饭是结果,什么也不干,一直在等着,同步加阻塞)
同步非阻塞:(等着打饭,但是可以玩会手机,看看电视,打饭是结果,但是不用一直在等着)
异步阻塞:(我要打饭,你说等着较好,并没有返回饭给我,我啥事不干,就干等着饭好了叫我)
异步非阻塞:回调的话。(我要打饭,你说等较好,并没有返回饭给我,可以在旁边看看电视,玩玩手机,饭好了叫我)
5)同步IO、异步IO、IO多路复用
IO模型:
IO分为两个阶段。
1)数据准备阶段。
2)内核空间复制回用户进程缓冲区阶段。
发生IO的时候:
1、内核从输入设备读、写数据(淘米,把米放锅里煮饭)
2、进程从内核复制数据(盛饭,从内核这个锅把饭装到碗里面来)
系统调用 -- read函数
从磁盘读取到内核空间中来,在拷贝到用户的应用空间内。
系统调用read函数。内核空间,用户空间。
5)IO模型
IO模型:同步IO,包括阻塞IO,非阻塞IO,IO多路复用。
阻塞IO
进程等待(阻塞),直到读写完成。(全程等待) read/write函数
进程调用read操作,如果IO设备没有准备好,立即返回error,进程不阻塞,用户可以再次 发起系统调用,如果内核已经准备好了,就阻塞,然后复制数据到用户空间。
第一阶段数据没有准备好,就先忙别的,等来再来看看,检查数据是否准备好了的过程是非阻塞的。
第二阶段是阻塞的,即内核空间和用户空间之间复制数据是阻塞的。
淘米、蒸饭不用等,去完后。盛饭过程等着你装好饭,但是要等到盛好饭才算完事,这个是同步的,结果就是盛好饭。
Read/write
IO多路复用:
IO多路复用,就是同时监控多个IO,有一个准备好了,就不需要等了开始处理,提高了同同时处理IO的能力。
Select几乎所有操作系统平台都支持,poll是对select的升级。
epoll,Linux系统内核2.5+开始的。对select和poll的增强,在监视的基础上,增加回调机制,BSD。Mac平台都有kqueue,window是有iocp
回调:
Select为例,将关注的IO操作告诉select函数并调用,进程阻塞,内核监视select关注的文件描述符fd,被关注的任何一个fd对应的IO准备好了数据,select返回,在使用read将数据复制到用户进程。
Select:最多监听1024个fd。IO多了,每次都要遍历fd全部发送,效率低下。
epoll:通知机制。没有fd的上限,且是回调机制。效率很高。
6)异步IO:
异步的性能是非常高的。
进程发起异步IO请求,立即返回,内核完成IO的两个阶段,内核给进程发一个信号。
举例:今天不想出去吃饭了,点外卖,饭菜在饭店做好了(第一阶段),送餐员会从饭店送到家门口(第二阶段)
7)Python中IO多路复用:
IO多路复用:
大多数操作系统支持select和poll
Linux2.5+支持epoll
BSD、mac支持kqueue。
Windows的IOcp
Python中的select库。
实现了select,poll系统调用,这个基本上操作系统都支持,部分实现了poll,底层的IO多路复用模块。
开发中的选择
1)完全跨平台,使用select,poll,但是性能较差。
2)针对不同操作系统自行选择支持的技术,这样做会提高IO处理的性能。
要访问的IO交给select,由其注册,
selectors库:
3.4版本提供的库,高级IO复用库。
BaseSelector
+-- SelectSelector
+-- PollSelector
+-- EpollSelector
+-- DevpollSelector
+-- KqueueSelector
Selectors.Defaultselector返回当前平台最有效、效能最高的实现。
但是没有实现windows下的iocp,所以,只能退化为select。
Abstractmethod register(fileobj,events,data=None)
为selector注册一个文件对象,监视他的IO事件。
Fileobj被监视文件对象,例如socket对象。
Events事件,该文件对象必须等待的时间。
Data 可选的与此文件对象相关联的不透明数据,例如,关联用户存储每个客户端的会话ID,关联方法,通过参数在关注的事件产生后让selector干什么事。
Event常量 |
含义 |
Event_read |
可读0b01,内核已经准备好输入输出设备,可以开始读了 |
Event_write |
可写0b10,内核准备好了,可以往里面写了。 |
import selectors
import threading
import socket
import logging
FORMAT = '%(asctime)s %(threadName)s %(thread)d %(message)s'
logging.basicConfig(format=FORMAT,level=logging.INFO)
def accept(sock:socket.socket,mask):
conn,raddr = sock.accept()
conn.setblocking(False)
key = selector.register(conn,selectors.EVENT_READ,read)
logging.info(key)
def read(conn:socket.socket,mask):
data = conn.recv(1024)
msg = 'msg is {]'.format(data.decode())
conn.send(msg.encode())
selector = selectors.DefaultSelector()
sock = socket.socket()
sock.bind(('127.0.0.1',8080))
sock.listen()
logging.info(sock)
sock.setblocking(False)
key = selector.register(sock,selectors.EVENT_READ,accept)
logging.info(key)
e = threading.Event()
def select(e):
while not e.is_set():
events = selector.select()
print('========')
for key,mask in events:
logging.info(key)
logging.info(mask)
callback = key.data
callback(key.fileobj,mask)
threading.Thread(target=select,args=(e,),name='select').start()
def main():
while not e.is_set():
cmd = input('>>>')
if cmd.strip() == 'quit':
e.set()
fobjs = []
logging.info('{}'.format(list(selector.get_map().items())))
for fd ,key in selector.get_map().items():
print(fd)
print(key)
fobjs.append(key.fileobj)
for fobj in fobjs:
selector.unregister(fobj)
fobj.close()
selector.close()
if __name__ == '__main__':
main()