python進階(七)~~~多線程并發
一、并發和并行
并發:任務數>CPU核數,通過系統的各任務調度算法,來回切換,實現多個任務“一起”運行,實際上不是真正同時一起運行,只是切換運行的速度相當快,看上去是一起執行的而已;
并行:任務數<=CPU核數,是真正的一起同時運行。
同步:同步是指代碼調用IO操作時,必須等待IO操作完成返回才調用的方式,只有一個主線;
異步:異步是指代碼調用IO操作時,不必等待IO操作完成返回才調用的方式,存在多條主線;
from threading import Thread import time def timer(fun): def wrapper(*args,**kwargs): time1 = time.time() fun(*args,**kwargs) time2=time.time() print("當前函數運行時間為:{}".format(time2-time1)) return time2-time1 return wrapper def work1(): for i in range(6): time.sleep(1) print(f'第{i}次澆花中') def work2(name): for i in range(5): time.sleep(1) print(f'第{i}次{name}打墻中') # 同步運行如下,需用11s多 @timer def main(): work1() work2() #異步運行,只用6s多 @timer def main2(): t1=Thread(target=work2,args=('musen',)) # 線程執行函數的傳參或者 Thread(target=work2,kwargs={'name':'musen'}) 這種方式傳參
t1.start() # 初始新線程的準備工作并執行,不一定在主線程后執行,也有可能先于主線程 print("打墻任務異步執行中") work1() main() main2()
threading模塊講解:
創建線程對象: t1=threading.Thread(target=func) func為指定線程執行的任務函數
Thread類常用方法、屬性:
1.start() :啟動線程活動。
2.run():這個方法描述線程的活動,可以在子類中覆蓋這個方法。
3.join(timeout=None):設置主線程等待子線程執行結束后再繼續運行??梢栽O定一個timeout參數,避免無休止的等待。因為兩個線程順序完成,看起來象一個線程,所以稱為線程的合并.
通過傳給join一個參數來設置超時,也就是超過指定時間join就不在阻塞進程。而在實際應用測試的時候發現并不是所有的線程在超時時間內都結束的,而是順序執行檢驗是否在time_out時間內超時,例如,超時時間設置成2s,前面一個線程在沒有完成的情況下,后面線程執行join會從上一個線程結束時間起再設置2s的超時。
4.name:線程名
5.getname(): 返回線程名
6.setname(): 設置線程名
7.ident:線程的標識符
8.is_alive(): 返回線程是否活動
9.daemon:布爾值,表示這個線程是否是守護線程
threading.active_count():這個函數返回當前線程的個數,這個程序的值也等于列表
threading.current_thread():返回當前的線程對象
threading.enumereate():返回當前運行線程的列表
threading.main_thread():返回主線程,正常情況下,主線程就是python 解釋器運行時開始的那個線程。
# 定義線程類
import time from threading import Thread def timer(fun): def wrapper(*args,**kwargs): time1 = time.time() fun(*args,**kwargs) time2=time.time() print("當前函數運行時間為:{}".format(time2-time1)) return time2-time1 return wrapper class MyTread(Thread):
def __init__(self):
super().__init__(*args,**keargs)
self.url=url
def run(self): headers={"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.80 afari/537.36"} for i in range(100): #res=requests.get('https://www.baidu.com',headers=headers)
res=requests.get(url=self.url,headers=headers) print(res) @timer def main(): t_list=[] for i in range(10): #t=MyTread()
t=MyTread('https://www.baidu.com') t.start() #t.join() 如果放在這里,就會變成單線程運行,一個執行完再建一個子線程 # 遍歷所有子線程,設置子線程全部執行完后再執行主線程 for j in t_list: j.join() if __name__=="__main__": main()
多線程可以共享全局變量(使用同一塊內存),修改會造出資源競爭,因此python同一進程里的多線程不可能實現并行,只能是并發,在線程中來回切換。
線程之間哪些操作會引起切換?
1. IO耗時操作:網絡、文件、輸入等耗時的IO操作,會自動進行線程切換;
2. 線程執行時間達到一定的閥值時會執行切換;
100萬bug的解決方法:
1. 通過加鎖來處理
2. 通過存儲隊列來做
from threading import Thread,Lock num=0 def work1():
global num for i in range(1000000): meta.acquire() # 加鎖 num+=1 meta .release() # 解鎖 def work2():
global num for i in range(1000000): meta.acquire() # 加鎖 num+=1 meta .release() # 解鎖 meta=Lock() # 創建一把鎖 def main(): t1=Thread(target=work1) t2=Thread(target=work2) t1.start() t2.start() t1.join() t2.join() print(num) if __name__=='__main__': main()
GIL全局解釋器鎖:
IO密集型任務: CPU占用少,大部分時間在IO操作等待上。這種適合多線程來完成
CPU密集型任務:CPU占用多,需要進行大量的計算。這種適合單線程來完成。
關于GIL的幾點說明:
1.python語言和GIL沒有關系,僅僅是由于歷史原因在Cpython虛擬機(解釋器)上,難以移除GIL;
2.GIL:全局解釋器鎖,每個線程在執行中都需要先獲取GIL,保證同一時刻只有一個線程可以執行代碼;
3. 線程釋放GIL鎖的情況,在IO等操作可能引起阻塞的system call之前,可以暫時釋放GIL,執行完畢后必須獲取GIL,python3使用計時器執行時間達到閥值時(python2使用tickets記數達到100)當前線程釋放GIL
4. python多進程可以使用多核的CPU資源
死鎖:
在多線程中,線程可以通過互斥鎖來保證對同一資源的唯一占有,但當程序變得復雜后,可能會出現線程 A 對資源 A 上了鎖,而線程 A 后邊需要用到資源 B,使用完畢后才會對資源 A解鎖,而線程 B 對資源 B 上了鎖,它后邊選要用到資源 A,用過后才會給 B 解鎖,如果線程 A 和線程 B 同時運行,就可能會造成一種情況:線程 A 在等待線程 B 解鎖,線程 B 也在等待線程 A 解鎖,這就是死鎖問題。
解決方法:? 死鎖問題應該盡量在設計程序時避免,或添加等待超時時間,從而檢測程序是否產生了死鎖,另一種就是通過銀行家算法也可以避免死鎖問題
銀行家算法:? 銀行家算法的思想就是,假設銀行有 10 元,這個時候有三個人提出貸款,A 要貸款 9 元,B 要貸款 3 元,C 要貸款 8 元,這時,銀行肯定不夠將所有人都滿足,銀行家算法就誕生了
? 這時銀行為了留住所有客戶并且保證自己的錢不會不足,便分批貸款給客戶,先借給 A 2 元、B 2 元、C 4 元,銀行還剩 2 元,此時 B 直需要再借 1 元就滿足了他自己的需求,銀行便借給他 1 元,自己剩 1 元,當 B 用完,將 3 元還給銀行后,銀行再將這 4 元借給 C,C 也就滿足了,等 C 還款后,再將 8 元中的 7 元借給 A,這樣便動態的滿足了三個客戶的需求
? 銀行家算法在程序中實際上也是模擬了銀行貸款的過程,操作系統會動態的向各個線程分配資源,在分配前,系統會判斷分配后會不會導致系統進入不安全狀態,不會就分配資源給線程,會則令線程等待

浙公網安備 33010602011771號