1.自定義進程
自定義進程類,繼承Process類,重寫run方法(重寫Process的run方法)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
from multiprocessing import Process import time import os class MyProcess(Process): def __init__( self , name): ##重寫,需要__init__,也添加了新的參數(shù)。 ##Process.__init__(self) 不可以省略,否則報錯:AttributeError:'XXXX'object has no attribute '_colsed' Process.__init__( self ) self .name = name def run( self ): print ( "子進程(%s-%s)啟動" % ( self .name, os.getpid())) time.sleep( 3 ) print ( "子進程(%s-%s)結(jié)束" % ( self .name, os.getpid())) if __name__ = = '__main__' : print ( "父進程啟動" ) p = MyProcess( "Ail" ) # 自動調(diào)用MyProcess的run()方法 p.start() p.join() print ( "父進程結(jié)束" ) |
# 輸出結(jié)果
父進程啟動
子進程(Ail-38512)啟動
子進程(Ail-38512)結(jié)束
父進程結(jié)束
2.進程與線程
多進程適合在CPU密集型操作(CPU操作指令比較多,如科學(xué)計算、位數(shù)多的浮點計算);
多線程適合在IO密集型操作(讀寫數(shù)據(jù)操作比較多的,比如爬蟲、文件上傳、下載)
線程是并發(fā),進程是并行:進程之間互相獨立,是系統(tǒng)分配資源的最小單位,同一個進程中的所有線程共享資源。
進程:一個運行的程序或代碼就是一個進程,一個沒有運行的代碼叫程序。進程是系統(tǒng)進行資源分配的最小單位,進程擁有自己的內(nèi)存空間,所以,進程間數(shù)據(jù)不共享,開銷大。
進程是程序的一次動態(tài)執(zhí)行過程。每個進程都擁有自己的地址空間、內(nèi)存、數(shù)據(jù)棧以及其它用于跟蹤執(zhí)行的輔助數(shù)據(jù)。操作系統(tǒng)負責(zé)其上所有進程的執(zhí)行,操作系統(tǒng)會為這些進程合理地分配執(zhí)行時間。
線程:調(diào)度執(zhí)行的最小單位,也叫執(zhí)行路徑,不能獨立存在,依賴進程的存在而存在,一個進程至少有一個線程,叫做主線程,多個線程共享內(nèi)存(數(shù)據(jù)共享和全局變量),因此提升程序的運行效率。
線程是操作系統(tǒng)能夠進行運算調(diào)度的最小單位,它被包含在進程之中,是進程中的實際運作單位。一條線程指的是進程中一個單一順序的控制流,一個進程中可以并發(fā)多個線程,每條線程并行執(zhí)行不同的任務(wù)。一個線程是一個execution context(執(zhí)行上下文),即一個CPU執(zhí)行時所需要的一串指令。
主線程:主線程就是創(chuàng)建線程進程中產(chǎn)生的第一個線程,也就是main函數(shù)對應(yīng)的線程。
協(xié)程:用戶態(tài)的輕量級線程,調(diào)度由用戶控制,擁有自己的寄存器上下文和棧,切換基本沒有內(nèi)核切換的開銷,切換靈活。
進程和線程的關(guān)系
3.多線程
操作系統(tǒng)通過給不同的線程分配時間片(CPU運行時長)來調(diào)度線程,當(dāng)CPU執(zhí)行完一個線程的時間片后就會快速切換到下一個線程,時間片很短而且切換速度很快,以至于用戶根本察覺不到。多個線程根據(jù)分配的時間片輪流被CPU執(zhí)行,如今絕大多數(shù)計算機的CPU都是多核的,多個線程在操作系統(tǒng)的調(diào)度下,能夠被多個CPU并行執(zhí)行,程序的執(zhí)行速度和CPU的利用效率大大提升。絕大對數(shù)主流的編程語言都能很好地支持多線程,然而,Python由于GIL鎖無法實現(xiàn)真正的多線程。
內(nèi)存中的線程
4.Thread類方法
(1)start()
--開始執(zhí)行該線程;
(2)run()
--定義線程的方法(開發(fā)者可以在子類中重寫);標(biāo)準(zhǔn)的 run() 方法會對作為 target 參數(shù)傳遞給該對象構(gòu)造器的可調(diào)用對象(如果存在)發(fā)起調(diào)用,并附帶從 args 和 kwargs 參數(shù)分別獲取的位置和關(guān)鍵字參數(shù)。
(3)join(timeout=None)
--直至啟動的線程終止之前一直掛起;除非給出了timeout(單位秒),否則一直被阻塞;因為 join() 總是返回 None ,所以要在 join() 后調(diào)用 is_alive() 才能判斷是否發(fā)生超時 -- 如果線程仍然存活,則 join() 超時。一個線程可以被 join() 很多次。如果嘗試加入當(dāng)前線程會導(dǎo)致死鎖, join() 會引起 RuntimeError 異常。如果嘗試 join() 一個尚未開始的線程,也會拋出相同的異常。
(4)is_alive()
--布爾值,表示這個線程是否還存活;當(dāng) run() 方法剛開始直到 run() 方法剛結(jié)束,這個方法返回 True 。
(5)threading.current_thread()--
返回當(dāng)前對應(yīng)調(diào)用者的控制線程的 Thread 對象。例如,獲取當(dāng)前線程的名字,可以是current_thread().name
。
5.多線程與多進程小Case
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
from threading import Thread from multiprocessing import Process import os def work(): print( 'hello,' ,os.getpid()) if __name__ == '__main__' : # 在主進程下開啟多個線程,每個線程都跟主進程的pid一樣 t1 = Thread(target=work) # 開啟一個線程 t2 = Thread(target=work) # 開啟兩個線程 t1.start() ##start()--It must be called at most once per thread object.It arranges for the object's run() method to be ## invoked in a separate thread of control.This method will raise a RuntimeError if called more than once on the ## same thread object. t2.start() print( '主線程/主進程pid' , os.getpid()) # 開多個進程,每個進程都有不同的pid p1 = Process(target=work) p2 = Process(target=work) p1.start() p2.start() print( '主線程/主進程pid' ,os.getpid()) |
6.Thread 的生命周期
線程的狀態(tài)包括:創(chuàng)建、就緒、運行、阻塞、結(jié)束。
(1)創(chuàng)建對象時,代表 Thread 內(nèi)部被初始化;
(2) 調(diào)用 start() 方法后,thread 會開始進入隊列準(zhǔn)備運行,在未獲得CPU、內(nèi)存資源前,稱為就緒狀態(tài);輪詢獲取資源,進入運行狀態(tài);如果遇到sleep,則是進入阻塞狀態(tài);
(3) thread 代碼正常運行結(jié)束或者是遇到異常,線程會終止。
7.自定義線程
(1)定義一個類,繼承Thread;
(2)重寫__init__ 和 run();
(3)創(chuàng)建線程類對象;
(4)啟動線程。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
import time import threading class MyThread(threading.Thread): def __init__( self ,num): super ().__init__() ###或者是Thread.__init__() self .num = num def run( self ): print ( '線程名稱:' , threading.current_thread().getName(), '參數(shù):' , self .num, '開始時間:' , time.strftime( '%Y-%m-%d %H:%M:%S' )) if __name__ = = '__main__' : print ( '主線程開始:' ,time.strftime( '%Y-%m-%d %H:%M:%S' )) t1 = MyThread( 1 ) t2 = MyThread( 2 ) t1.start() t2.start() t1.join() t2.join() print ( '主線程結(jié)束:' , time.strftime( '%Y-%m-%d %H:%M:%S' )) |
8.線程共享數(shù)據(jù)與GIL(全局解釋器鎖)
如果是全局變量,則每個線程是共享的;
GIL鎖:可以用籃球比賽的場景來模擬,把籃球場看作是CPU,一場籃球比賽看作是一個線程,如果只有一個籃球場,多場比賽就要排隊進行,類似于一個簡單的單核多線程的程序;如果由多塊籃球場,多場比賽同時進行,就是一個簡單的多核多線程的程序。然而,Python有著特別的規(guī)定:每場比賽必須要在裁判的監(jiān)督之下才允許進行,而裁判只有一個。這樣不管你有幾塊籃球場,同一時間只允許有一個場地進行比賽,其它場地都將被閑置,其它比賽都只能等待。
9.GIL 和 Lock
GIL保證同一時間內(nèi)一個進程可以有多個線程,但只有一個線程在執(zhí)行;鎖的目的是為了保護共享的數(shù)據(jù),同一時間只能有一個線程來修改共享的數(shù)據(jù)。
類為threading.Lock
它有兩個基本方法, acquire() 和 release() 。
當(dāng)狀態(tài)為非鎖定時, acquire() 將狀態(tài)改為 鎖定 并立即返回。當(dāng)狀態(tài)是鎖定時, acquire() 將阻塞至其他線程調(diào)用 release() 將其改為非鎖定狀態(tài),然后 acquire() 調(diào)用重置其為鎖定狀態(tài)并返回。
release() 只在鎖定狀態(tài)下調(diào)用; 它將狀態(tài)改為非鎖定并立即返回。如果嘗試釋放一個非鎖定的鎖,則會引發(fā) RuntimeError 異常。
Caese 如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
from threading import Thread from threading import Lock import time number = 0 def task(lock): global number lock.acquire() ##持有鎖 for i in range ( 100000 ) number + = 1 lock.release() ##釋放鎖 if __name__ = = '__main__' : lock = Lock() t1 = Thread(target = task,args = (lock,)) t2 = Thread(target = task,args = (lock,)) t3 = Thread(target = task,args = (lock,)) t1.start() t2.start() t3.start() t1.join() t2.join() t3.join() print ( 'number:' ,number) |
10.線程的信號量
class threading.Semaphore([values])
values是一個內(nèi)部計數(shù),values默認是1,如果小于0,則會拋出 ValueError 異常,可以用于控制線程數(shù)并發(fā)數(shù)。
信號量的實現(xiàn)方式:
s=Semaphore(?)
在內(nèi)部有一個counter計數(shù)器,counter的值就是同一時間可以開啟線程的個數(shù)。每當(dāng)我們s.acquire()一次,計數(shù)器就進行減1處理,每當(dāng)我們s.release()一次,計數(shù)器就會進行加1處理,當(dāng)計數(shù)器為0的時候,其它的線程就處于等待的狀態(tài)。
程序添加一個計數(shù)器功能(信號量),限制一個時間點內(nèi)的線程數(shù)量,防止程序崩潰或其它異常。
Case
1
2
3
4
5
6
7
8
9
10
11
12
|
import time import threading s = threading.Semaphore( 5 ) #添加一個計數(shù)器 def task(): s.acquire() #計數(shù)器獲得鎖 time.sleep( 2 ) #程序休眠2秒 print ( "The task run at " ,time.ctime()) s.release() #計數(shù)器釋放鎖 for i in range ( 40 ): t1 = threading.Thread(target = task,args = ()) #創(chuàng)建線程 t1.start() #啟動線程 |
也可以使用with操作,替代acquire ()和release(),上面的代碼調(diào)整如下:
1
2
3
4
5
6
7
8
9
10
11
12
|
import time import threading s = threading.Semaphore( 5 ) #添加一個計數(shù)器 def task(): with s: ## 類似打開文件的with操作 ##s.acquire() #計數(shù)器獲得鎖 time.sleep( 2 ) #程序休眠2秒 print ( "The task run at " ,time.ctime()) ##s.release() #計數(shù)器釋放鎖 for i in range ( 40 ): t1 = threading.Thread(target = task,args = ()) #創(chuàng)建線程 t1.start() #啟動線程 |
建議使用with。
總結(jié)
本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關(guān)注服務(wù)器之家的更多內(nèi)容!
原文鏈接:https://www.cnblogs.com/xuliuzai/p/15488546.html