Python學(xué)習(xí)筆記之線(xiàn)程
1.自定義進(jìn)程
自定義進(jìn)程類(lèi),繼承Process類(lèi),重寫(xiě)run方法(重寫(xiě)Process的run方法)。
from multiprocessing import Process
import time
import os
class MyProcess(Process):
def __init__(self, name): ##重寫(xiě),需要__init__,也添加了新的參數(shù)。 ##Process.__init__(self) 不可以省略,否則報(bào)錯(cuò):AttributeError:'XXXX'object has no attribute '_colsed'
Process.__init__(self)
self.name = name
def run(self):
print("子進(jìn)程(%s-%s)啟動(dòng)" % (self.name, os.getpid()))
time.sleep(3)
print("子進(jìn)程(%s-%s)結(jié)束" % (self.name, os.getpid()))
if __name__ == '__main__':
print("父進(jìn)程啟動(dòng)")
p = MyProcess("Ail")
# 自動(dòng)調(diào)用MyProcess的run()方法
p.start()
p.join()
print("父進(jìn)程結(jié)束")
# 輸出結(jié)果
父進(jìn)程啟動(dòng)
子進(jìn)程(Ail-38512)啟動(dòng)
子進(jìn)程(Ail-38512)結(jié)束
父進(jìn)程結(jié)束
2.進(jìn)程與線(xiàn)程
多進(jìn)程適合在CPU密集型操作(CPU操作指令比較多,如科學(xué)計(jì)算、位數(shù)多的浮點(diǎn)計(jì)算);
多線(xiàn)程適合在IO密集型操作(讀寫(xiě)數(shù)據(jù)操作比較多的,比如爬蟲(chóng)、文件上傳、下載)
線(xiàn)程是并發(fā),進(jìn)程是并行:進(jìn)程之間互相獨(dú)立,是系統(tǒng)分配資源的最小單位,同一個(gè)進(jìn)程中的所有線(xiàn)程共享資源。
進(jìn)程:一個(gè)運(yùn)行的程序或代碼就是一個(gè)進(jìn)程,一個(gè)沒(méi)有運(yùn)行的代碼叫程序。進(jìn)程是系統(tǒng)進(jìn)行資源分配的最小單位,進(jìn)程擁有自己的內(nèi)存空間,所以,進(jìn)程間數(shù)據(jù)不共享,開(kāi)銷(xiāo)大。
進(jìn)程是程序的一次動(dòng)態(tài)執(zhí)行過(guò)程。每個(gè)進(jìn)程都擁有自己的地址空間、內(nèi)存、數(shù)據(jù)棧以及其它用于跟蹤執(zhí)行的輔助數(shù)據(jù)。操作系統(tǒng)負(fù)責(zé)其上所有進(jìn)程的執(zhí)行,操作系統(tǒng)會(huì)為這些進(jìn)程合理地分配執(zhí)行時(shí)間。
線(xiàn)程:調(diào)度執(zhí)行的最小單位,也叫執(zhí)行路徑,不能獨(dú)立存在,依賴(lài)進(jìn)程的存在而存在,一個(gè)進(jìn)程至少有一個(gè)線(xiàn)程,叫做主線(xiàn)程,多個(gè)線(xiàn)程共享內(nèi)存(數(shù)據(jù)共享和全局變量),因此提升程序的運(yùn)行效率。
線(xiàn)程是操作系統(tǒng)能夠進(jìn)行運(yùn)算調(diào)度的最小單位,它被包含在進(jìn)程之中,是進(jìn)程中的實(shí)際運(yùn)作單位。一條線(xiàn)程指的是進(jìn)程中一個(gè)單一順序的控制流,一個(gè)進(jìn)程中可以并發(fā)多個(gè)線(xiàn)程,每條線(xiàn)程并行執(zhí)行不同的任務(wù)。一個(gè)線(xiàn)程是一個(gè)execution context(執(zhí)行上下文),即一個(gè)CPU執(zhí)行時(shí)所需要的一串指令。
主線(xiàn)程:主線(xiàn)程就是創(chuàng)建線(xiàn)程進(jìn)程中產(chǎn)生的第一個(gè)線(xiàn)程,也就是main函數(shù)對(duì)應(yīng)的線(xiàn)程。
協(xié)程:用戶(hù)態(tài)的輕量級(jí)線(xiàn)程,調(diào)度由用戶(hù)控制,擁有自己的寄存器上下文和棧,切換基本沒(méi)有內(nèi)核切換的開(kāi)銷(xiāo),切換靈活。
進(jìn)程和線(xiàn)程的關(guān)系

3.多線(xiàn)程
操作系統(tǒng)通過(guò)給不同的線(xiàn)程分配時(shí)間片(CPU運(yùn)行時(shí)長(zhǎng))來(lái)調(diào)度線(xiàn)程,當(dāng)CPU執(zhí)行完一個(gè)線(xiàn)程的時(shí)間片后就會(huì)快速切換到下一個(gè)線(xiàn)程,時(shí)間片很短而且切換速度很快,以至于用戶(hù)根本察覺(jué)不到。多個(gè)線(xiàn)程根據(jù)分配的時(shí)間片輪流被CPU執(zhí)行,如今絕大多數(shù)計(jì)算機(jī)的CPU都是多核的,多個(gè)線(xiàn)程在操作系統(tǒng)的調(diào)度下,能夠被多個(gè)CPU并行執(zhí)行,程序的執(zhí)行速度和CPU的利用效率大大提升。絕大對(duì)數(shù)主流的編程語(yǔ)言都能很好地支持多線(xiàn)程,然而,Python由于GIL鎖無(wú)法實(shí)現(xiàn)真正的多線(xiàn)程。
內(nèi)存中的線(xiàn)程

4.Thread類(lèi)方法
(1)start() --開(kāi)始執(zhí)行該線(xiàn)程;
(2)run() --定義線(xiàn)程的方法(開(kāi)發(fā)者可以在子類(lèi)中重寫(xiě));標(biāo)準(zhǔn)的 run() 方法會(huì)對(duì)作為 target 參數(shù)傳遞給該對(duì)象構(gòu)造器的可調(diào)用對(duì)象(如果存在)發(fā)起調(diào)用,并附帶從 args 和 kwargs 參數(shù)分別獲取的位置和關(guān)鍵字參數(shù)。
(3)join(timeout=None) --直至啟動(dòng)的線(xiàn)程終止之前一直掛起;除非給出了timeout(單位秒),否則一直被阻塞;因?yàn)?join() 總是返回 None ,所以要在 join() 后調(diào)用 is_alive() 才能判斷是否發(fā)生超時(shí) -- 如果線(xiàn)程仍然存活,則 join() 超時(shí)。一個(gè)線(xiàn)程可以被 join() 很多次。如果嘗試加入當(dāng)前線(xiàn)程會(huì)導(dǎo)致死鎖, join() 會(huì)引起 RuntimeError 異常。如果嘗試 join() 一個(gè)尚未開(kāi)始的線(xiàn)程,也會(huì)拋出相同的異常。
(4)is_alive() --布爾值,表示這個(gè)線(xiàn)程是否還存活;當(dāng) run() 方法剛開(kāi)始直到 run() 方法剛結(jié)束,這個(gè)方法返回 True 。
(5)threading.current_thread()--返回當(dāng)前對(duì)應(yīng)調(diào)用者的控制線(xiàn)程的 Thread 對(duì)象。例如,獲取當(dāng)前線(xiàn)程的名字,可以是current_thread().name。
5.多線(xiàn)程與多進(jìn)程小Case
from threading import Thread
from multiprocessing import Process
import os
def work():
print('hello,',os.getpid())
if __name__ == '__main__':
# 在主進(jìn)程下開(kāi)啟多個(gè)線(xiàn)程,每個(gè)線(xiàn)程都跟主進(jìn)程的pid一樣
t1 = Thread(target=work) # 開(kāi)啟一個(gè)線(xiàn)程
t2 = Thread(target=work) # 開(kāi)啟兩個(gè)線(xiàn)程
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('主線(xiàn)程/主進(jìn)程pid', os.getpid())
# 開(kāi)多個(gè)進(jìn)程,每個(gè)進(jìn)程都有不同的pid
p1 = Process(target=work)
p2 = Process(target=work)
p1.start()
p2.start()
print('主線(xiàn)程/主進(jìn)程pid',os.getpid())
6.Thread 的生命周期
線(xiàn)程的狀態(tài)包括:創(chuàng)建、就緒、運(yùn)行、阻塞、結(jié)束。
(1)創(chuàng)建對(duì)象時(shí),代表 Thread 內(nèi)部被初始化;
(2) 調(diào)用 start() 方法后,thread 會(huì)開(kāi)始進(jìn)入隊(duì)列準(zhǔn)備運(yùn)行,在未獲得CPU、內(nèi)存資源前,稱(chēng)為就緒狀態(tài);輪詢(xún)獲取資源,進(jìn)入運(yùn)行狀態(tài);如果遇到sleep,則是進(jìn)入阻塞狀態(tài);
(3) thread 代碼正常運(yùn)行結(jié)束或者是遇到異常,線(xiàn)程會(huì)終止。
7.自定義線(xiàn)程
(1)定義一個(gè)類(lèi),繼承Thread;
(2)重寫(xiě)__init__ 和 run();
(3)創(chuàng)建線(xiàn)程類(lèi)對(duì)象;
(4)啟動(dòng)線(xiàn)程。
import time
import threading
class MyThread(threading.Thread):
def __init__(self,num):
super().__init__() ###或者是Thread.__init__()
self.num = num
def run(self):
print('線(xiàn)程名稱(chēng):', threading.current_thread().getName(), '參數(shù):', self.num, '開(kāi)始時(shí)間:', time.strftime('%Y-%m-%d %H:%M:%S'))
if __name__ == '__main__':
print('主線(xiàn)程開(kāi)始:',time.strftime('%Y-%m-%d %H:%M:%S'))
t1 = MyThread(1)
t2 = MyThread(2)
t1.start()
t2.start()
t1.join()
t2.join()
print('主線(xiàn)程結(jié)束:', time.strftime('%Y-%m-%d %H:%M:%S'))
8.線(xiàn)程共享數(shù)據(jù)與GIL(全局解釋器鎖)
如果是全局變量,則每個(gè)線(xiàn)程是共享的;
GIL鎖:可以用籃球比賽的場(chǎng)景來(lái)模擬,把籃球場(chǎng)看作是CPU,一場(chǎng)籃球比賽看作是一個(gè)線(xiàn)程,如果只有一個(gè)籃球場(chǎng),多場(chǎng)比賽就要排隊(duì)進(jìn)行,類(lèi)似于一個(gè)簡(jiǎn)單的單核多線(xiàn)程的程序;如果由多塊籃球場(chǎng),多場(chǎng)比賽同時(shí)進(jìn)行,就是一個(gè)簡(jiǎn)單的多核多線(xiàn)程的程序。然而,Python有著特別的規(guī)定:每場(chǎng)比賽必須要在裁判的監(jiān)督之下才允許進(jìn)行,而裁判只有一個(gè)。這樣不管你有幾塊籃球場(chǎng),同一時(shí)間只允許有一個(gè)場(chǎng)地進(jìn)行比賽,其它場(chǎng)地都將被閑置,其它比賽都只能等待。
9.GIL 和 Lock
GIL保證同一時(shí)間內(nèi)一個(gè)進(jìn)程可以有多個(gè)線(xiàn)程,但只有一個(gè)線(xiàn)程在執(zhí)行;鎖的目的是為了保護(hù)共享的數(shù)據(jù),同一時(shí)間只能有一個(gè)線(xiàn)程來(lái)修改共享的數(shù)據(jù)。
類(lèi)為threading.Lock
它有兩個(gè)基本方法, acquire() 和 release() 。
當(dāng)狀態(tài)為非鎖定時(shí), acquire() 將狀態(tài)改為 鎖定 并立即返回。當(dāng)狀態(tài)是鎖定時(shí), acquire() 將阻塞至其他線(xiàn)程調(diào)用 release() 將其改為非鎖定狀態(tài),然后 acquire() 調(diào)用重置其為鎖定狀態(tài)并返回。
release() 只在鎖定狀態(tài)下調(diào)用; 它將狀態(tài)改為非鎖定并立即返回。如果嘗試釋放一個(gè)非鎖定的鎖,則會(huì)引發(fā) RuntimeError 異常。
Caese 如下:
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.線(xiàn)程的信號(hào)量
class threading.Semaphore([values])
values是一個(gè)內(nèi)部計(jì)數(shù),values默認(rèn)是1,如果小于0,則會(huì)拋出 ValueError 異常,可以用于控制線(xiàn)程數(shù)并發(fā)數(shù)。
信號(hào)量的實(shí)現(xiàn)方式:
s=Semaphore(?)
在內(nèi)部有一個(gè)counter計(jì)數(shù)器,counter的值就是同一時(shí)間可以開(kāi)啟線(xiàn)程的個(gè)數(shù)。每當(dāng)我們s.acquire()一次,計(jì)數(shù)器就進(jìn)行減1處理,每當(dāng)我們s.release()一次,計(jì)數(shù)器就會(huì)進(jìn)行加1處理,當(dāng)計(jì)數(shù)器為0的時(shí)候,其它的線(xiàn)程就處于等待的狀態(tài)。
程序添加一個(gè)計(jì)數(shù)器功能(信號(hào)量),限制一個(gè)時(shí)間點(diǎn)內(nèi)的線(xiàn)程數(shù)量,防止程序崩潰或其它異常。
Case
import time
import threading
s=threading.Semaphore(5) #添加一個(gè)計(jì)數(shù)器
def task():
s.acquire() #計(jì)數(shù)器獲得鎖
time.sleep(2) #程序休眠2秒
print("The task run at ",time.ctime())
s.release() #計(jì)數(shù)器釋放鎖
for i in range(40):
t1=threading.Thread(target=task,args=()) #創(chuàng)建線(xiàn)程
t1.start() #啟動(dòng)線(xiàn)程
也可以使用with操作,替代acquire ()和release(),上面的代碼調(diào)整如下:
import time
import threading
s=threading.Semaphore(5) #添加一個(gè)計(jì)數(shù)器
def task(): with s:## 類(lèi)似打開(kāi)文件的with操作
##s.acquire() #計(jì)數(shù)器獲得鎖
time.sleep(2) #程序休眠2秒
print("The task run at ",time.ctime())
##s.release() #計(jì)數(shù)器釋放鎖
for i in range(40):
t1=threading.Thread(target=task,args=()) #創(chuàng)建線(xiàn)程
t1.start() #啟動(dòng)線(xiàn)程
建議使用with。
總結(jié)
本篇文章就到這里了,希望能夠給你帶來(lái)幫助,也希望您能夠多多關(guān)注本站的更多內(nèi)容!
版權(quán)聲明:本站文章來(lái)源標(biāo)注為YINGSOO的內(nèi)容版權(quán)均為本站所有,歡迎引用、轉(zhuǎn)載,請(qǐng)保持原文完整并注明來(lái)源及原文鏈接。禁止復(fù)制或仿造本網(wǎng)站,禁止在非maisonbaluchon.cn所屬的服務(wù)器上建立鏡像,否則將依法追究法律責(zé)任。本站部分內(nèi)容來(lái)源于網(wǎng)友推薦、互聯(lián)網(wǎng)收集整理而來(lái),僅供學(xué)習(xí)參考,不代表本站立場(chǎng),如有內(nèi)容涉嫌侵權(quán),請(qǐng)聯(lián)系alex-e#qq.com處理。
關(guān)注官方微信