10月13日学习内容整理:线程,创建线程(threading模块),守护线程,GIL(全局解释器互斥锁)...
一、線程
1、概念:一條流水線的工作過程
2、和進程的區別和關系
(1)關系
》進程是資源單位,線程是執行單位,cpu真正執行的是線程
》一個進程至少有一個線程
》多線程針對的是一個進程的概念
》從執行的角度說:執行一個進程就相當于開啟一個控制(主)線程
》從資源的角度說:開啟一個進程就是開辟一個內存空間
(2)區別
》核心1:創建線程開銷很小,不用申請空間(比進程快10-100倍);而進程開銷大需要申請空間
》核心2:同一進程的多個線程共享該進程的資源和地址空間;但是不同進程的地址空間是隔離的
》線程可以直接訪問進程的數據;而子進程是會拷貝父進程的所有數據
》同一進程的多個線程之間可以直接通信;而不同的進程通信則要依靠IPC機制
》線程可以控制同一進程的其它線程;進程只能控制它的子進程
》改變主線程可能貴影響其它線程的行為;改變父進程不會影響子進程
3、和進程的對比
》線程沒有“父子”概念
》同一進程的不同線程的進程ID都是一樣的
》由于線程共享進程的資源,所以當線程對數據修改時修改的就是該進程內存空間中的數據,但要注意線程和控制線程(該進程)對數據處理的時間先后順序
?
二、創建線程
1、代碼實現:Thread類(threading模塊)
t = Thread(target=函數名,args=(參數,)/kwargs={字典}) ? 實例化
t.start() ? 發送創建線程請求(由于創建線程開銷很小,所以發送請求的同時基本就開啟了線程)
t .join() ? ? 主線程等待線程結束
from threading import Thread from multiprocessing import Processdef work(n):print('%s is running' %n)if __name__ == '__main__':t=Thread(target=work,args=(1,))t.start()print('主線程')?
2、其它方法:
(1)對象方法
》isAlive() 線程是否存活
》getName() ?獲取當前線程的名字,默認Thread-1,Thread-2,...這樣
》setName() ? 設置當前線程的名字
(2)模塊提供的方法
》currentThread() 獲取當前線程對象
》enumerate() 顯示當前活躍線程,輸出列表
》activeCount() ?當前活躍線程的數目
?
三、守護線程
1、代碼實現:
t.daemon=True ? ?必須在start()之前設置
2、概念
》當控制線程(從執行角度上看是該進程)結束守護線程也隨之結束
》控制線程只有當所有非守護線程都結束時才算結束
from threading import Thread import time def foo():print(123)time.sleep(5)print("end123")def bar():print(456)time.sleep(3)print("end456")if __name__ == '__main__':t1=Thread(target=foo)t2=Thread(target=bar)t1.daemon=Truet1.start()t2.start()print("main-------")?
四、GIL:全局解釋器互斥鎖(不是python的特性)
1、產生背景:在Cpython解釋器中特有的,因為python解釋器帶有數據回收機制,這樣就會帶來回收和利用的沖突性,所以就要求一個進程的多個線程同一時間只能有一個被執行
2、本質:就是一把互斥鎖,實現將并發變為串行,保證數據安全
》》》#1 所有數據都是共享的,這其中,代碼作為一種數據也是被所有線程共享的(test.py的所有代碼以及Cpython解釋器的所有代碼)
》》》例如:test.py定義一個函數work,在進程內所有線程都能訪問到work的代碼,于是我們可以開啟三個線程然后target都指向該代碼,能訪問到意味著就是可以執行。
》》》#2 所有線程的任務,都需要將任務的代碼當做參數傳給解釋器的代碼去執行,即所有的線程要想運行自己的任務,首先需要解決的是能夠訪問到解釋器的代碼。
?
在一個python的進程內,不僅有test.py的主線程或者由該主線程開啟的其他線程,還有解釋器開啟的垃圾回收等解釋器級別的線程,總之,所有線程都運行在這一個進程內,毫無疑問
?
3、實現方式:
with ?lock:
python解釋器的代碼()
這個lock就是GIL
?
?
4、GIL和lock:
》要根據不同的數據來加不同的鎖,因為GIL保證不了用戶數據的安全,GIL只能保護python解釋器級別的數據
》線程要想被執行就要去搶GIL鎖(也就是爭執行權限)
?
5、GIL和多線程
》對于多個計算密集型的程序
(1)單核情況下:因為始終只有一個cpu去執行計算,所以就要求開銷要小,故選擇多線程
(2)多核情況下:因為有多個cpu去執行計算,能夠實現真正的并行,效率會很高,而線程每次只有一個在執行沒有利用多核的優勢,故選擇多進程
》對于多個IO密集型程序
(1)單核情況下:因為只有一個cpu執行,需要不斷的切換來實現并發,所以要求開銷盡量要小,故選擇多線程
(2)多核情況下:多個cpu執行的話,遇到IO仍然要阻塞,若是多進程的話就會常常是阻塞狀態,運行時間就會是時間最長的哪個程序,而如果是多線程,首先開銷很小,其次每次執行一個線程遇到IO阻塞就會立即去處理別的線程,這樣效率會提高,故選擇多線程
?
6、為什么要有GIL和代碼運行的整個過程
》首先,python解釋器帶有的數據回收機制,本身是有計數器的,通過計數來判斷是否為垃圾數據,而這個計數就是python解釋器中的代碼提供的功能(其實也是一段程序來實現)也就是python解釋器級別的數據,而肯定有可能會出現當python解釋器要回收某一個數據而某一個程序正好要使用這個數據的情況,這就帶來了沖突,為了解決這種沖突保護數據安全就必須保證同一時間內只能有一個程序來使用python解釋器的代碼,這就變成了串行,所以就引入了GIL鎖的應用
》我們寫一段python代碼,首先肯定要打開python解釋器,這就相當于開啟了一個代碼解釋器的進程,我們在解釋器中寫代碼其實就是用了解釋器的功能(也就是通過一段程序實現的功能),而代碼本身是不會自己運行的,運行我們所寫的代碼其實就是開啟了另一個進程(即是python解釋器進程的子進程)也就是將我們所寫的代碼作為參數傳給python解釋器來執行(這其實就是相當于子進程創建時會把父進程(python解釋器的代碼)的所有數據都拷貝,這樣子進程(我們所寫的代碼)才能運行),而由于GIL鎖的存在,每個程序要想運行就要去搶這把鎖也就是爭取運行權限,爭到了就執行若遇到阻塞操作系統就會強制釋放掉該程序的運行權限(也就是釋放掉GIL鎖),這樣別的程序再去搶然后運行,等之前的程序處于就緒態時會再去搶GIL鎖接著之前的運行狀態繼續執行,這就是一段代碼要想運行起來必須經歷的過程
補充一下:
1、開啟python解釋器操作系統其實是將解釋器的代碼從硬盤中取出放到內存中去運行,我們所寫的代碼也是這樣的
2、其實進程和線程的創建和開啟(個數快慢等,當然線程的開銷肯定要比進程小很多),包括程序運行時對GIL鎖的爭搶和釋放以及運行狀態的保留等,這些都是由操作系統及電腦性能來控制和決定的,無法由程序本身來操控
》上面說到每個程序運行前要搶GIL鎖(運行權限),運行起來時就是按照自己的代碼來運行,會產生自己的數據,而這個數據GIL是保護不了的,因為GIL只是鎖住了解釋器而沒有鎖住每個進程對同一塊數據的處理功能,所以這就是為什么不同的數據要加不同的鎖來保護,GIL只是保護python解釋器代碼級別的數據而已
?
轉載于:https://www.cnblogs.com/wanghl1011/articles/7663035.html
總結
以上是生活随笔為你收集整理的10月13日学习内容整理:线程,创建线程(threading模块),守护线程,GIL(全局解释器互斥锁)...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: [js插件开发教程]一步步开发一个可以定
- 下一篇: 炉石传说邪火试炼玛瑟里顿怎么打 玛瑟里顿