Python線程編程之Thread詳解
一、線程編程(Thread)
1、線程基本概念
1.1、什麼事線程
- 線程被稱為輕量級的進程
- 線程也可以使用計算機多核資源,是多任務編程方式
- 線程是系統分配內核的最小單元
- 線程可以理解為進程的分支任務
1.2、線程特征
- 一個進程中可以包含多個線程
- 線程也是一個運行行為,消耗計算機資源
- 一個線程中的所有線程共享這個進程的資源
- 多個線程之間的運行互不影響各自運行
- 線程的創建和銷毀消耗資源遠小於進程
- 各個線程也有自己的ID等特征
二、threading模塊創建線程
1、創建線程對象
from threading import Thread t = Thread() 功能: 創建線程對象 參數: target 綁定線程函數 args 元組 給線程函數位置傳參 kwargs 字典 給線程函數鍵值傳參
2、 啟動線程
t.start()
3、 回收線程
t.join([timeout])
4、代碼演示
""" thread1.py 線程基礎使用 步驟: 1. 封裝線程函數 2.創建線程對象 3.啟動線程 4.回收線程 """ import os from threading import Thread from time import sleep a = 1 # 線程函數 def music(): for i in range(3): sleep(2) print('播放:黃河大合唱 %s' % os.getpid()) global a print("a,",a) a = 1000 # 創建線程對象 t = Thread(target=music) # 啟動線程 t.start() for i in range(3): sleep(1) print('播放:beauty love %s' % os.getpid()) # 回收線程 t.join() print('程序結束') print("a,", a)
5、線程對象屬性
1.t.name
線程名稱
2.t.setName()
設置線程名稱
3.t.getName()
獲取線程名稱
4.t.is_alive()
查看線程是否在生命周期
5.t.daemon
設置主線程和分支線程退出分支線程也退出.要在start前設置 通常不和join 一起使用
6.代碼演示
""" thread3.py 線程屬性演示 """ from threading import Thread from time import sleep def fun(): sleep(3) print('線程屬性測試') t = Thread(target=fun, name='ceshi') # 主線程退出分支線程也退出 必須在start前使用 與join 沒有意義 t.setDaemon(True) t.start() print(t.getName()) t.setName('Tedu') print('is alive:', t.is_alive()) print('daemon', t.daemon)
6、自定義線程類
1.創建步驟
1.繼承Thread類
2.重寫 __init__方法添加自己的屬性 使用super加載父類屬性
3.重寫run方法
2.使用方法
1.實例化對象
2.調傭start自動執行run方法
3.調傭join回收線程
代碼演示
""" 自定義線程類例子 """ from threading import Thread # 自定義線程類 class ThreadClass(Thread): # 重寫父類 init def __init__(self, *args, **kwargs): self.attr = args[0] # 加載父類init super().__init__() # 假設需要很多步驟完成功能 def f1(self): print('1') def f2(self): print(2) # 重寫run 邏輯調傭 def run(self): self.f1() self.f2() t = ThreadClass() t.start() t.join()
7、一個很重要的練習 我很多不懂
from threading import Thread from time import sleep, ctime class MyThread(Thread): def __init__(self, group=None, target=None, name=None, args=(), kwargs=None, *, daemon=None): super().__init__() self.fun = target self.args = args self.kwargs = kwargs def run(self): self.fun(*self.args, **self.kwargs) def player(sec, song): for i in range(3): print("Playing %s : %s" % (song, ctime())) sleep(sec) t = MyThread(target=player, args=(3,), kwargs={'song': '量量'}) t.start() t.join()
8、線程間通信
1.通信方法
1.線程間使用全局遍歷進行通信
2.共享資源爭奪
1.共享資源:多個進程或者線程都可以操作的資源稱為共享資源,對共享資源的操作代碼段稱為臨界區
2.影響:對公共資源的無序操作可能會帶來數據的混亂,或者操作錯誤.此時往往需要同步互斥機制協調操作順序
3.同步互斥機制
1.同步:同步是一種協作關系,為完成操作,多進程或者線程形成一種協調,按照必要的步驟有序執行操作
2.互斥:互斥是一種制約關系,當一個進程或者線程占有資源時,會進行加鎖處理,此時其它進程線程就無法操作該資源,直到解鎖後才能操作
## 9.線程同步互斥方法
1. 線程Event 代碼演示
from threading import Event # 創建線程event對象 e = Event() # 阻塞等待e被set e.wait([timeout]) # 設置e, 使wait結束阻塞 e.set() # 使e回到未被設置狀態 e.clear() # 查看當前e是否被設置 e.is_set()
""" event 線程互斥方法演示 """ from threading import Event, Thread s = None # 用於通信 e = Event() def yzr(): print('楊子榮前來拜山頭') global s s = '天王蓋地虎' e.set() #操作完共享資源 e設置 t = Thread(target=yzr) t.start() print('說對口令就是自己人') e.wait() #阻塞等待 e.set() if s == '天王蓋地虎': print('寶塔鎮河妖') print('確認過眼神,你是對的人') e.clear() else: print('打死他...') t.join() print('程序結束')
2. 線程鎖 Lock代碼演示
from threading import Lock lock = Lock()創建鎖對象 lock.acquire() 上鎖 如果lock已經上鎖再調用會阻塞 lock.release() 解鎖 with lock: 上鎖 .... .... with 代碼塊解鎖自動解鎖
""" thread_lock 線程鎖演示 """ from threading import Thread, Lock a = b = 0 lock = Lock() def value(): while True: # 上鎖 lock.acquire() print('a=%d,b=%d' % (a, b)) if a != b else print('a不等於b') # 解鎖 lock.release() t = Thread(target=value) t.start() while True: # with 開始上鎖 with lock: a += 1 b += 1 # with 解鎖 自動解鎖 t.join() print('程序結束')
10、死鎖及其處理
1.定義
死鎖是指兩個或者兩個以上的線程在執行過程中,由於競爭資源或者由於彼此通信而造成的一種阻塞的現象,若無外力作用,他們都將無法推進下去.此時稱系統處於死鎖狀態或系統產生瞭死鎖.
2.圖解
3. 死鎖產生條件
死鎖發生的必要條件
- 互斥條件:指線程對所分配到的資源進行排它性使用,即在一段時間內某資源隻由一個進程占用。如果此時還有其它進程請求資源,則請求者隻能等待,直至占有資源的進程用畢釋放。
- 請求和保持條件:指線程已經保持至少一個資源,但又提出瞭新的資源請求,而該資源已被其它進程占有,此時請求線程阻塞,但又對自己已獲得的其它資源保持不放。
- 不剝奪條件:指線程已獲得的資源,在未使用完之前,不能被剝奪,隻能在使用完時由自己釋放,通常CPU內存資源是可以被系統強行調配剝奪的。
- 環路等待條件:指在發生死鎖時,必然存在一個線程——資源的環形鏈,即進程集合{T0,T1,T2,···,Tn}中的T0正在等待一個T1占用的資源;T1正在等待T2占用的資源,……,Tn正在等待已被T0占用的資源。
- 死鎖的產生原因
簡單來說造成死鎖的原因可以概括成三句話:
- 當前線程擁有其他線程需要的資源
- 當前線程等待其他線程已擁有的資源
- 都不放棄自己擁有的資源
如何避免死鎖
死鎖是我們非常不願意看到的一種現象,我們要盡可能避免死鎖的情況發生。通過設置某些限制條件,去破壞產生死鎖的四個必要條件中的一個或者幾個,來預防發生死鎖。預防死鎖是一種較易實現的方法。但是由於所施加的限制條件往往太嚴格,可能會導致系統資源利用率。
4.死鎖代碼演示
from time import sleep from threading import Thread, Lock # 交易類 class Account: def __init__(self, _id, balance, lock): # 用戶 self._id = _id # 存款 self.balance = balance # 鎖 self.lock = lock # 取錢 def withdraw(self, amount): self.balance -= amount # 存錢 def deposit(self, amount): self.balance += amount # 餘額 def get_balance(self): return self.balance Tom = Account('Tom', 5000, Lock()) Alex = Account('Alex', 8000, Lock()) def transfer(from_, to, amount): # 鎖住自己賬戶 if from_.lock.acquire(): # 賬戶減少 from_.withdraw(amount) sleep(0.5) if to.lock.acquire(): to.deposit(amount) to.lock.release() from_.lock.release() print('轉賬完成 %s給%s轉賬%d' % (from_._id, to._id, amount)) # transfer(Tom, Alex, 1000) t1 = Thread(target=transfer, args=(Tom, Alex, 2000)) t2 = Thread(target=transfer, args=(Alex, Tom, 3500)) t1.start() t2.start() t1.join() t2.join() print('程序結束')
python線程GIL
1.python線程的GIL問題 (全局解釋器鎖)
什麼是GIL :由於python解釋器設計中加入瞭解釋器鎖,導致python解釋器同一時刻隻能解釋執行一個線程,大大降低瞭線程的執行效率。
導致後果: 因為遇到阻塞時線程會主動讓出解釋器,去解釋其他線程。所以python多線程在執行多阻塞高延遲IO時可以提升程序效率,其他情況並不能對效率有所提升。
GIL問題建議
- 盡量使用進程完成無阻塞的並發行為
- 不使用c作為解釋器 (Java C#)
總結:
在無阻塞狀態下,多線程程序和單線程程序執行效率幾乎差不多,甚至還不如單線程效率。但是多進程運行相同內容卻可以有明顯的效率提升。
本篇文章就到這裡瞭,希望能夠給你帶來幫助,也希望您能夠多多關註WalkonNet的更多內容!