1、 IO模型linux
五種IO模型:程序員
blocking IO :阻塞IOweb
nonblocking IO 非阻塞IO數據庫
IO multiplexing IO多路複用編程
signal driven IO 信號驅動IOwindows
asynchronous IO 異步IO緩存
對於一個network IO,它會涉及到兩個系統對象,一個是調用這個IO的process(or thread),另外一個就是系統內核。當一個read/recv讀數據的操做發生時,該操做會經歷兩個階段:tomcat
1, 等待數據準備網絡
2, 將數據從內核拷貝到進程中多線程
補充:
1, 輸入操做:read,readv,recv,recvfrom,recvmsg共5個函數,若是會阻塞狀態,則會經歷wait data和copy data兩個階段,若是設置爲非阻塞則在wait不到data時拋出異常
2, 輸出操做:write,writev,send,sendto,sendmsg共5個函數,在發送緩衝區滿了會阻塞在原地,若是設置爲非阻塞,則會拋出異常
3, 接收外來連接:accept,與輸入操做相似
4, 發起外出連接:connect,與操做相似
2、阻塞IO
回顧同步/異步/阻塞/非阻塞:
同步:提交一個任務以後要等待這個任務執行完畢
異步:只管提交任務,不等待這個任務執行完畢就能夠去作其餘的事情
阻塞:recv,recvfrom,accept,線程階段 運行狀態>>>阻塞狀態>>>就緒
非阻塞:沒有阻塞狀態
在一個線程的IO模型中,咱們recv的地方阻塞,咱們就開啓多線程,可是無論你開啓多少個線程,這個recv的時間是否是沒有被規避掉,不論是多線程仍是多進程都沒有規避掉IO這個時間
實際上,除非特別指定,幾乎全部的IO接口(包括socket接口)都是阻塞的。這給網絡編程帶來了一個很大的問題,如在調用recv(1024)的同時,線程將被阻塞,在此期間,線程將沒法執行任何運算或響應任何的網絡請求
一個簡單的解決方案:
在服務端使用多線程(或多進程)。多線程(或多進程)的目的是讓每一個鏈接都擁有獨立的線程(或進程),這樣任何一個鏈接的阻塞都不會影響其餘的鏈接。
該方案的問題是:
開啓多線程或都線程的方式,在遇到要同時響應成百上千的鏈接請求,則不管多線程仍是多進程都會佔用嚴重的系統資源,下降系統對外界相應效率,並且線程與進程自己也更容易進入假死狀態
改進方案:
不少程序員可能會考慮使用「線程池」或「進程池」。「線程池」旨在減小建立和銷燬線程的頻率,其維持必定合理數量的線程,並讓空閒的線程從新承擔新的執行任務。「鏈接池」維持鏈接的緩存池,儘可能重用已有的鏈接、減小建立和關閉鏈接的頻率。這兩種技術均可以很好的下降系統開銷,都被普遍應用很大型系統,如websphere、tomcat和各類數據庫等
改進後方案其實也存在着問題:
「線程池」和「鏈接池」技術也只是在必定程度上緩解了頻繁調用IO接口帶來的資源佔用。並且,所謂「池」始終有其上限,當請求大大超過上限時,「池」構成的系統對外界的響應並不比沒有池的時候效果好多少。因此使用「池」必須其面臨的響應規模,並根據規模調整「池」的大小
3、非阻塞IO
缺點:
1, 循環調用recv()將大幅推高cpu佔用率,這也是咱們在代碼中留一句time.sleep(2)的緣由,不然在低配主機下極容易出現卡機狀況
2, 任務完成的響應延遲增大了,由於每過一段時間纔去輪詢一次read一次,而任務可能在兩次輪詢之間的任意時間完成。這會致使總體數據吞吐量的下降。
import time import socket server = socket.socket() ip_port = ('127.0.0.1',8001) server.bind(ip_port) server.listen() server.setblocking(False) conn_list = [] while 1: while 1: try: conn,addr = server.accept() conn_list.append(conn) break except BlockingIOError: time.sleep(0.1) print('此時尚未人連接我') for sock in conn_list: print(sock) while 1: try: from_client_msg = sock.recv(1024) print(from_client_msg.decode('utf-8')) sock.send(b'hello') break except BlockingIOError: print('尚未任何的消息啊')
import socket client = socket.socket() client.connect(('127.0.0.1',8001)) while 1: to_server_msg = input('我想對你說>>>>') client.send(to_server_msg.encode('utf-8')) from_server_msg = client.recv(1024) print(from_server_msg.decode('utf-8'))
4、多路複用IO
結論:select的優點在於能夠處理多個鏈接,不適用於單個鏈接
IO多路複用的機制:
Select:windows 、linux
Poll機制:linux和select監聽機制同樣,可是對監聽列表裏面的數量沒有限制,select默認限制是1024個,可是他們兩個都是操做系統輪詢每個被監聽的文件描述符(若是數量很大,其實效率不太好),卡是否有可讀操做
Epoll:linux它的監聽機制和上面兩個不一樣,他給每個監聽的對象綁定了一個回調函數,你這個對象有消息,那麼觸發回調函數給用戶,用戶就進行系統調用來拷貝數據,並非輪詢監聽全部的被監聽對象,這樣的效率高不少。
import select import socket server = socket.socket() server.bind(('127.0.0.1',8001)) rlist = [server,] server.listen() while 1: print('11111') rl,wl,el = select.select(rlist,[],[])#建立rl對象,監聽 print(222222) print('server對象>>>',server) print(rl) #rl對象其實跟server對象(內容)一致 for sock in rl: #當rl有值的時候,循環列表 if sock == server: #值與server相同 conn,addr = sock.accept() #創建鏈接 rlist.append(conn) #把管道信息加入列表 else: from_client_msg = sock.recv(1024)#conn print(from_client_msg.decode('utf-8')) #打印接收
import socket client = socket.socket() client.connect(('127.0.0.1',8001)) to_server_msg = input('發給服務端的消息:') client.send(to_server_msg.encode('utf-8')) # from_server_msg = client.recv(1024) # print(from_server_msg.decode('utf-8'))
5、異步IO
from gevent import monkey;monkey.patch_all() import time import gevent def func1(n): print('xxxxxx',n) # gevent.sleep(2) time.sleep(2) print('cccccc',n) def func2(m): print('111111',m) # gevent.sleep(2) time.sleep(2) print('222222',m) start_time = time.time() g1 = gevent.spawn(func1,'alex') g2 = gevent.spawn(func2,'德瑪西亞') # g1.join() # # g2.join() gevent.joinall([g1,g2]) end_time = time.time() print(end_time - start_time) print('代碼結束')