標籤:標準 程式 自己的 關聯 獨立 預設 加鎖 非同步io erp
進程和線程的基礎知識
CPU執行代碼是順序執行, 單核CPU通過讓任務交替執行, "類比"除了多任務並發執行. 真正的多任務並發, 是在多核CPU上, 每個CPU負責執行一個任務. 但實際任務數量遠多於CPU核心數量, 所以最終還是作業系統把多任務輪流調度到不同的核心上執行.
進程/線程和實體記憶體(寄存器)/CPU的關聯: 函數調用, 會在棧中分配一塊空間, 存放局部變數和參數, 調用結束, 棧空間被釋放. 每個線程都有獨立的棧, 寄存器. 同一進程裡的所有線程共用檔案, 代碼和資料.
進程獨立性: 以CPU中的程式計數器PC為例, 物理的PC只有一個, 但是每個進程有獨立的邏輯PC, 儲存了程式運行中的值, 但CPU輪到該進程時, 就將邏輯PC值複製到物理PC.
Python跨平台支援多進程
Unix/Linux平台Python支援fork(): 父進程複製出一個子進程
Windows平台Python支援Process(target,args): "類比出fork()的效果", 即父進程所有Python對象pickle後傳遞給子進程
Python處理序間通訊的資料交換方式
multiprocessing模組提供的Queue, Pipes
Python多線程基礎知識
標準庫提供了兩個模組: thread和threading(推薦).
任務進程都預設啟動一個叫做MainThread的主線程, 主線程可以啟動新的子線程.
多線程的變數鎖: 進階語言的一條語句在CPU執行時是若干條語句, 計算中的結果會存入臨時變數中, 每個線程都有自己的臨時變數.
擷取鎖後一定要釋放, 否則等待鎖的線程會一直阻塞下去, 成為死線程. Python中可以通過try...finally確保釋放.
鎖的壞處: (1) 加鎖的代碼只能以單線程模式執行, 阻止了多線程並發, 降低效率; (2) 可能造成死結.
Python的GIL鎖導致多線程不能充分利用多核CPU
Python的解譯器有一個GIL(Global Interpreter Lock)鎖, 任何線程執行前, 先獲得GIL鎖, 每執行100行代碼, 解譯器會自動釋放GIL鎖, 讓別的線程有機會執行. 所以即使100個線程跑在100核CPU上, 也只能用到1個核.
GIL是Python解譯器設計的曆史遺留問題, 使得Python不能有效利用多核, 但可以通過多進程實現.
多個Python進程有各自獨立的GIL鎖, 互不影響.
多線程下選擇加鎖的全域變數or不得不傳遞下去的局部變數?
加鎖和傳遞局部變數都是老方法了, 只是局部變數傳遞起來很麻煩. 如果遇到每個線程一個專屬對象, 不能使用全域變數的情況, 作者就想到用一個全域dict, 但是通過線程自身作為key獲得儲存的對應的對象, 例如:
1 # -*-coding:utf-8 -*- 2 import time, threading 3 4 global_dict = {} 5 6 class Student(object): 7 8 def __init__(self, name): 9 self.name = name10 11 def return_name(self):12 return self.name13 14 15 def do_task_1():16 std = Student(‘Elsa‘)17 global_dict[threading.currentThread()] = std18 std = global_dict[threading.current_thread()]19 print ‘thread %s, do_task_1() std.name = %s\n‘ % (threading.currentThread().name, std.return_name())20 21 def do_task_2():22 std = Student(‘Anna‘)23 global_dict[threading.currentThread()] = std24 std = global_dict[threading.current_thread()]25 print ‘thread %s, do_task_2() std.name = %s\n‘ % (threading.currentThread().name, std.return_name())26 27 28 if __name__ == ‘__main__‘:29 print ‘thread %s is running...‘ % threading.currentThread().name30 t1 = threading.Thread(target=do_task_1, name=‘t1‘)31 t2 = threading.Thread(target=do_task_2, name=‘t2‘)32 t1.start()33 t2.start()34 t1.join()35 t2.join()36 print ‘thread %s ended.‘ % threading.currentThread().name
global_dict = {}作為一個全域變數, 儲存了線程和它的專屬對象. 運行結果:
D:\WorkspaceVSCode>python multiprocess.pythread MainThread is running...thread t1, do_task_1() std.name = Elsathread t2, do_task_2() std.name = Annathread MainThread ended.
然後Python又簡化了對每個線程專屬對象的儲存和訪問, 也就是封裝了上面的global_dict = {}. 可以理解成, local_school = threading.local()是一個全域變數, 但它裡面儲存的是各個線程的局部變數, 還不用管理線程鎖了, 感覺很方便的樣子.
多線程時使用ThreadLocal用全域變數儲存線程的局部變數
常用於為每個線程綁定一個資料庫連接, HTTP請求, 使用者身份資訊等.
進程, 線程和計算密集, IO密集型任務
多進程更穩定, 系統的子進程互相記憶體獨立, 子進程崩潰不會影響其它進程(主進程除外)
多線程更高效, 但由於所有線程共用進程的記憶體, 一個崩潰會引發系統強制結束整個進程
計算密集型任務, 如視頻高清解碼, 主要開銷在CPU計算, 任務數=核心數最好
IO密集型任務, 如網路, 磁碟IO等, CPU消耗很少, 大部分時間都是在等待IO操作完成, 任務越多, CPU效率越高(有限度)
不能孤立的看待CPU和IO操作間巨大的速度差異
既然一個任務在執行時大部分時間都是CPU等待IO, 單進程單線程並不能充分的利用CPU資源, 在CPU等待IO的時候, 不放用空閑時間去執行CPU計算任務. "現代作業系統對IO操作已經做出了巨大改進, 最大的特點就是支援非同步IO. 如果充分利用作業系統提供的非同步IO支援, 就可以用單進程單執行緒模式來執行多任務." (Android裡的AsyncTask就是一種非同步, 但是它本質是另外起了一個小線程, 所以非同步就是多線程?)
學習筆記_廖雪峰<Python 2.7 教程>