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()