1、local 在多個線程之間使用threading.local對象,能夠實現多個線程之間的數據隔離 import time import random from threading import Thread,local loc = local() def func1(): global loc print(loc.name,loc.age) def func2(name,age): global loc loc.name = name loc.age = age time.sleep(random.uniform(0,2)) func1() Thread(target=func2,args=('明哥',19)).start() Thread(target=func2,args=('小弟',17)).start() 結果: 小弟 17 明哥 19 解釋: 在平時的線程中,應該每次打印的結果應該都是最後進來的線程的數據,即第一次的數據會被第二次的數據覆蓋, 可是使用了local對象,能夠實現線程的數據隔離,所以打印的兩次結果都不同。 原理: local對象會建立一個大字典,字典的鍵是線程的id,值也是一個字典,存儲數據,例如: { 線程id1:{'name':'明哥',age:19} 線程id2:{'name':'小弟',age:17} ... } 2、 1、阻塞IO(blocking IO) 當用戶進程調用了recv/recvfrom這些系統調用時,操做系統就開始了IO的第一個階段:準備數據。由於不少時候數據一開始尚未到達(好比,尚未收到一個完整的UDP包),這個時候操做系統就要等待足夠的數據到來。 而在用戶進程這邊,整個進程會被阻塞。操做系統一直等到數據準備好了,它就會將數據拷貝到用戶內存,而後返回結果,用戶進程才解除block的狀態,從新運行起來。 因此,blocking IO的特色就是在IO執行的兩個階段(等待數據和拷貝數據兩個階段)都被block了。 幾乎全部的程序員第一次接觸到的網絡編程都是從listen()、send()、recv() 等接口開始的,使用這些接口能夠很方便的構建服務器/客戶機的模型。然而大部分的socket接口都是阻塞型的。 阻塞型接口是指系統調用(通常是IO接口)不返回調用結果並讓當前線程一直阻塞,只有當該系統調用得到結果或者超時出錯時才返回。 實際上,除非特別指定,幾乎全部的IO接口 ( 包括socket接口 ) 都是阻塞型的。這給網絡編程帶來了一個很大的問題,如在調用recv(1024)的同時,線程將被阻塞,在此期間,線程將沒法執行任何運算或響應任何的網絡請求。 解決方案: 在服務器端使用多線程(或多進程)。多線程(或多進程)的目的是讓每一個鏈接都擁有獨立的線程(或進程),這樣任何一個鏈接的阻塞都不會影響其餘的鏈接。 該方案的問題是: 開啓多進程或都線程的方式,在遇到要同時響應成百上千路的鏈接請求,則不管多線程仍是多進程都會嚴重佔據系統資源,下降系統對外界響應效率,並且線程與進程自己也更容易進入假死狀態。 改進方案: 不少程序員可能會考慮使用「線程池」或「鏈接池」。「線程池」旨在減小建立和銷燬線程的頻率,其維持必定合理數量的線程,並讓空閒的線程從新承擔新的執行任務。「鏈接池」維持鏈接的緩存池,儘可能重用已有的鏈接、減小建立和關閉鏈接的頻率。這兩種技術均可以很好的下降系統開銷,都被普遍應用不少大型系統,如websphere、tomcat和各類數據庫等。 改進後方案其實也存在着問題: 「線程池」和「鏈接池」技術也只是在必定程度上緩解了頻繁調用IO接口帶來的資源佔用。並且,所謂「池」始終有其上限,當請求大大超過上限時,「池」構成的系統對外界的響應並不比沒有池的時候效果好多少。因此使用「池」必須考慮其面臨的響應規模,並根據響應規模調整「池」的大小。 對應上例中的所面臨的可能同時出現的上千甚至上萬次的客戶端請求,「線程池」或「鏈接池」或許能夠緩解部分壓力,可是不能解決全部問題。總之,多線程模型能夠方便高效的解決小規模的服務請求,但面對大規模的服務請求,多線程模型也會遇到瓶頸,能夠用非阻塞接口來嘗試解決這個問題。 2、非阻塞IO 非阻塞IO操做一些時,若是操做系統中的數據尚未準備好,它也不會出現阻塞,而是馬上返回一個error。用戶進程能夠判斷這個返回值是不是error,若是是它就知道數據尚未準備好,因而用戶就能夠在下次詢問前的時間內作其餘事情。若是數據準備好了,且用戶再次進行詢問時,那麼數據就能夠拷貝到用戶內存(這一階段仍然是阻塞的),而後返回。 其實在非阻塞式IO中,用戶進程實際上是須要不斷的主動詢問操做系統數據是否準備好了。 例如: Server端: import socket sk = socket.socket() sk.bind(('127.0.0.1',8080)) sk.setblocking(False) # 設置當前的server爲一個非阻塞IO模型 sk.listen() conn_lst = [] del_lst = [] while True: try: conn,addr = sk.accept() conn_lst.append(conn) except BlockingIOError: for conn in conn_lst: try: conn.send(b'hello') print(conn.recv(1024)) except (NameError,BlockingIOError):pass except ConnectionResetError: conn.close() del_lst.append(conn) for del_conn in del_lst: conn_lst.remove(del_conn) del_lst.clear() Client端: import socket sk = socket.socket() sk.connect(('127.0.0.1',8080)) while 1: print(sk.recv(1024)) sk.send(b'heiheihei') 這樣就能夠實現非阻塞IO下基於TCP的併發socket, 可是這樣的非阻塞IO大量的佔用了CPU致使了資源的浪費而且給CPU形成了很大的負擔 3、多路複用IO 當用戶進程調用了select,那麼整個進程會被block,同時,操做系統會「監視」全部select負責的socket,當任何一個socket中的數據準備好了,select就會返回。這個時候用戶進程再調用read操做,將數據從操做系統拷貝到用戶進程。 多路複用IO與阻塞IO: 多路複用IO須要使用兩次系統調用,而阻塞IO只調用了一個系統調用。可是,用select的優點在於它能夠同時處理多個鏈接,而阻塞IO只能處理一個。 例子: Server端: import select # 用來操做操做系統中的select(IO多路複用)機制 import socket sk = socket.socket() sk.bind(('127.0.0.1',8888)) sk.setblocking(False) sk.listen() r_lst = [sk,] while True: # 監視r_lst這個列表(全部待接收請求的對象都在這裏),誰的請求來了就通知誰,而後把有請求的對象返回給r_l列表,也有可能兩個請求一塊兒來 r_l,_,_ = select.select(r_lst,[],[]) for item in r_l: # 循環有請求的對象,判斷是什麼類型,進行對應的操做 if item is sk: conn,addr = sk.accept() r_lst.append(conn) else: try: print(item.recv(1024)) item.send(b'hello') except ConnectionResetError: item.close() r_lst.remove(item) Client端: import socket sk = socket.socket() sk.connect(('127.0.0.1',8888)) while 1: sk.send(b'world') print(sk.recv(1024)) 多路複用IO小結: io多路複用(asycio tornado twisted)機制 select 在windows\mac\linux可用 底層是操做系統的輪詢 有監聽對象個數的限制 隨着監聽對象的個數增長,效率下降 poll 只在mac\linux可用 底層是操做系統的輪詢 有監聽對象個數的限制,可是比select能監聽的個數多 隨着監聽對象的個數增長,效率下降 epoll 只在mac\linux可用 給每個要監聽的對象都綁定了一個回調函數 再也不受到個數增長 效率下降的影響 socketserver機制 IO多路複用 + threading線程 selectors模塊 幫助你在不一樣的操做系統上進行IO多路複用機制的自動篩選 四、異步IO(Asynchronous I/O) 用戶進程發起請求以後,操做系統會馬上返回,全部不會造成阻塞,而後用戶進程馬上就能夠開始去作其它的事。同時操做系統會等待數據準備完成,而後將數據拷貝到用戶內存,當這一切都完成以後,會給用戶進程發送一個signal,告訴它操做完成了。