GIL全局解釋器鎖
GIL即python全局解釋器鎖,這是一個存在於解釋器進程中的鎖,該鎖的存在造成了即使是多核cpu,在同一個python進程中,只會有一個線程被調度。如果想同時使用多核的優勢,就需要使用多個進程來全面利用cpu。
IO密集型和計算密集型
- IO密集型
IO密集型是指程序中有大量的IO操作,線程進行IO操作時,會進入阻塞態,阻塞態的線程不會接受CPU的線程調度,CPU的時間將分配給其他線程。即使只有一個cpu執行程序,cpu也可以輕松的調度大量IO密集的線程執行計算步驟。
- 計算密集型
計算密集型是指程序需要大量的占用CPU資源進行計算,幾乎沒有阻塞操作。由於GIL的存在,當線程A進行調度時,其余的線程處於阻塞狀態,CPU進行線程切換時,需要等待其他線程進入就緒態。而此時線程A剛被調度完成,還沒有進入阻塞,此時CPU可能又會將A線程進行調度,反復如此。導致其他線程幾乎無法使用CPU。
多進程
由於CPython解釋器的GIL鎖的問題,使得一個CPython進程只能同時執行一個線程,使用多線程的方式運行一個計算密集型的程序並不是一個好的選擇。想要提高cpu的利用率,我們就需要解決GIL的問題,既然一個CPython進程只能運行一個線程,那么使用多個Python進程就能夠解決我們的問題。
Python提供了使用多線程的庫multiprocessing來實現多線程運行。並提供了和多線程類似的API
import multiprocessing, time def func(): x = 0 for i in range(1000000000): # 10億次 x = x + 1 if __name__ == "__main__": start = time.time() m1 = multiprocessing.Process(target=func) m2 = multiprocessing.Process(target=func) m1.start() m2.start() m1.join() m2.join() end = time.time() print("total_time = ", end - start) ====結果==== total_time = 65.23427520193434
如果我們使用單進程執行
import multiprocessing, time def func(): x = 0 for i in range(1000000000): # 10億次 x = x + 1 if __name__ == "__main__": start = time.time() func() func() end = time.time() print("total_time =", end - start) =====運行結果===== total_time = 113.9023756980896
從結果可以看出使用多進程執行的時間大約為單進程執行的一半。
使用多進程雖然可以降低執行時間,但是多個進程實現數據共享代價是比較昂貴的,而在多線程中只需要使用一個全局變量即可實現。multiprocessing庫提供了進程間通訊的Queue隊列和Pipe管道實現數據通信。
開啟進程需要耗費系統資源,這比開啟一個線程的代價大得多,大量的使用多進程不是一個好的選擇。可以嘗試使用其他語言解決需求。並且每一個進程有一個唯一PID來標記該進程,PID是有限的系統資源,,所以在必須時候我們才使用多進程去處理任務。
進程池pool
進程池是提前開辟進程資源,將這個進程資源交由這個容器管理,當需要使用進程時,只需要向這個容器申請進程資源即可。這個容器就是mupltiprocessing.Pool對象。進程池中的進程資源可以反復利用,不需要反復的創建和銷毀進程資源。
實例化一個Pool類即可得到Pool對象,它提供以下方法。
| 方法 | 說明 |
| apply(func, args, kwds) | 阻塞執行,每次向該pool中提交一個任務,之后主進程阻塞等待任務完成,並得到func函數的返回值 |
| apply_async(func, args, kwds, callback, error_callback) | 異步執行,主線程不會阻塞等待,而是把所有的任務交給pool,由pool自己調度func函數執行,pool根據自己 |
| close() | 關閉進程池,池不再接受任務所有任務完成后退出 |
| terminate() | 立即結束工作進程,不再處理未處理的任務 |
| join() | 主進程阻塞等待子進程退出,必須在close()或者terminate()方法后使用 |
import multiprocessing, time, logging, sys def func(n): if n > 5: raise IndexError("n > 4-------------error") logging.info("func") time.sleep(3) logging.info("thi") return "return = 1000" def callback(value): logging.info("callback finished") print("value = ", value) def callback_error(value): logging.info("occur the error at") print("error-value = ", value) if __name__== "__main__": logging.basicConfig(stream=sys.stdout, level=10, format="%(msg)s: %(process)s-%(processName)s") pool = multiprocessing.Pool(3) for i in range(10): pool.apply_async(func, args=(i, ), callback=callback, error_callback=callback_error) logging.info("exit--for") pool.close() pool.join() print("------------------end")
這里定義了一個進程池可以同時處理3個進程,在for循環中提交了10個任務進行處理,主進程會將任務全部提交給pool處理,然后在pool.join()等待任務處理完畢。pool每次執行三個任務,執行完成全部任務后,主進程繼續執行,結束。
在pool.apply_async方法中的參數為
| 參數 | 含義 |
| func | 任務函數,在pool中啟動新的進程來執行該func函數 |
| args, kwds | 任務函數的參數,args為位置傳參:元組;kwds為關鍵字傳參:字典 |
| callback(value) | 傳入一個一參函數,該函數將接受func函數正常執行后的返回值作為參數。在該func任務執行完成后,由pool繼續調用callback函數執行 |
| error_callback(value) | 傳入一個一參函數,該函數參數將接受func函數的錯誤作為參數。如果func函數執行發生錯誤,調用該函數,正常執行調用callback函數。 |
Linux幾個特殊的進程
在unix/linux中,正常情況下,子進程是通過父進程創建的,子進程再創建新的進程。子進程的結束和父進程的運行是一個異步過程,即父進程永遠無法預測子進程到底什么時候結束。 當一個進程完成它的工作終止之后,它的父進程需要調用wait()或者waitpid()系統調用取得子進程的終止狀態。
unix提供了一種機制可以保證只要父進程想知道子進程結束時的狀態信息,就可以得到。這種機制就是: 在每個進程退出的時候,內核釋放該進程所有的資源,包括打開的文件,占用的內存等。但是仍然為其保留一定的信息(包括進程號the process ID,退出狀態the termination status of the process,運行時間the amount of CPU time taken by the process等)。直到父進程通過wait / waitpid來取時才釋放。但這樣就導致了問題,如果進程不調用wait / waitpid的話,那么保留的那段信息就不會釋放,其進程號就會一直被占用,但是系統所能使用的進程號是有限的,如果大量的產生僵死進程,將因為沒有可用的進程號而導致系統不能產生新的進程. 此即為僵屍進程的危害,應當避免。
孤兒進程
孤兒進程:一個父進程退出,而它的子進程還在運行,那么那些子進程將成為孤兒進程。孤兒進程將被init進程(進程號為1)所收養,並由init進程(1號進程)對它們完成狀態收集工作。並循環調用wait獲取這些孤兒進程,所以孤兒進程沒有什么危害。
僵屍進程
僵屍進程:一個進程使用fork創建子進程,如果子進程退出,而父進程並沒有調用wait或waitpid獲取子進程的狀態信息,那么子進程的進程描述符仍然保存在系統中,繼續占用內存空間。這種進程稱之為僵死進程。
守護進程
它是運行在后台的一種特殊進程,在其父進程結束后仍然正常運行並由init管理。它會周期性的執行某種任務或者等待處理某些事件。相對於普通的孤兒進程需要做一些特殊的處理。
