Python執行緒安全問題及解決方法
Python多執行緒是通過threading模組來實現的。
一、多執行緒共享全域性變數
from threading import Thread
list_a = [1, 2, 3]
def add_list():
global list_a
list_a.append(100)
print(list_a)
if __name__ == '__main__':
t1 = Thread(target=add_list)
t2 = Thread(target=add_list)
print(t1.name)
t1.start()
print(t2.name)
t2.start()
執行結果:
Thread-1
[1, 2, 3, 100]
Thread-2
[1, 2, 3, 100, 100]
在上面的程式碼中,我們建立了兩個執行緒,這兩個執行緒都是執行一次函式add_list,線上程t1執行完後,全域性變數list_a中多了一個100,線上程t2執行完後,list_a中多了兩個100,說明執行緒t2是線上程t1的基礎上進行新增的。也就是說t1和t2兩個執行緒是共享全域性變數的。
在一個程式內的所有執行緒共享全域性變數,很方便在多個執行緒間共享資料。
但是,多執行緒對全域性變數隨意修改可能造成全域性變數的混亂,產生執行緒安全問題。
二、多執行緒的資源競爭問題(執行緒非安全)
from threading import Thread
num = 0
def add_num():
global num
for i in range(100000):
num += 1
if __name__ == '__main__':
t1 = Thread(target=add_num)
t2 = Thread(target=add_num)
t3 = Thread(target=add_num)
t1.start()
t2.start()
t3.start()
print(num)
執行結果:
221845
在上面的程式碼中,我們建立了三個執行緒,每個執行緒都是將num進行十萬次+1運算,因為三個執行緒是共享全域性變數的,所以結果應該是三十萬300000。但是,結果卻少了很多(每次執行結果不一樣)。
在多個執行緒對全域性變數進行修改時,造成得到的結果不正確,這種情況就是執行緒安全問題。
如果多個執行緒同時對同一個全域性變數操作,會出現資源競爭問題,從而資料結果會不正確,即遇到執行緒安全問題。
那麼,為什麼多執行緒操作全域性變數時會有資源競爭問題呢?
先假設兩個執行緒t1和t2都要對全域性變數num(從0開始)進行加1運算,t1和t2都各對num加10次,num的最終的結果應該為20。
但是由於是多執行緒同時操作,有可能出現下面情況:
1.在num=0時,t1取得num=0,但還沒有開始做+1運算。此時系統把t1排程為”sleeping”狀態,把t2轉換為”running”狀態,t2也獲得num=0
2.然後t2對得到的值進行加1並賦給num,使得num=1
3.然後系統又把t2排程為”sleeping”,把t1轉為”running”。執行緒t1把它之前得到的0加1後賦值給num。
4.這樣導致雖然t1和t2都對num加1,但結果仍然是num=1
不過,一般在萬級運算次數以下,不會出現資源競爭問題,當上了十萬級或更高量級時,資源競爭問題會越來越明顯。當然,這與電腦的配置也有關。
三、通過同步機制來解決執行緒安全問題
from threading import Thread, Lock, enumerate
import time
num = 0
mutex = Lock()
def add_num():
global num
for i in range(100000):
mutex.acquire()
num += 1
mutex.release()
if __name__ == '__main__':
t1 = Thread(target=add_num)
t2 = Thread(target=add_num)
t3 = Thread(target=add_num)
t1.start()
t2.start()
t3.start()
while len(enumerate()) != 1:
time.sleep(1)
print(num)
執行結果:
300000
上面的程式碼中,我們給之前的三個執行緒中加了鎖,這樣最後執行的結果就是我們想要的結果。
這種方式是使用互斥鎖來實現同步,避免資源競爭問題發生。
除了使用互斥鎖可以保證執行緒同步外,還有其他方式可以實現同步,解決執行緒安全,如通過佇列來實現同步,因為佇列是序列的,底層封裝了鎖。
四、同步和互斥鎖
同步就是程式按預定的先後次序依次執行。
通過執行緒同步機制,能保證共享資料在任何時刻,最多有一個執行緒訪問,以保證資料的正確性。
注意:
1.執行緒同步就是執行緒排隊
2.共享資源的讀寫才需要同步
3.變數才需要同步,常量不需要同步
當多個執行緒幾乎同時修改某一個共享資料的時候,需要進行同步控制。
執行緒同步能夠保證多個執行緒安全地訪問競爭資源,最簡單的同步機制是使用互斥鎖。
互斥鎖為資源引入了一個狀態:鎖定/非鎖定
某個執行緒要更改共享資料時,先將其鎖定,此時資源的狀態為“鎖定”,其他執行緒不能更改。直到該執行緒釋放資源,將資源的狀態變成“非鎖定”,其他的執行緒才能再次鎖定該資源。互斥鎖保證了每次只有一個執行緒進行操作,從而保證了多執行緒情況下資料的正確性。
注意:
多個執行緒使用的是同一個鎖,如果資料沒有被鎖鎖上,那麼acquire()方法不會堵塞,會執行上鎖操作。
如果在呼叫acquire時,資料已經被其他執行緒上了鎖,那麼acquire()方法會堵塞,直到資料被解鎖為止。
上鎖解鎖過程:
當一個執行緒呼叫鎖的acquire()方法獲得鎖時,鎖就進入“locked”狀態。
每次只有一個執行緒可以獲得鎖。如果此時另一個執行緒試圖獲得這個鎖,該執行緒就會變為“blocked”狀態,稱為“阻塞”,直到擁有鎖的執行緒呼叫鎖的release()方法釋放鎖之後,鎖進入“unlocked”狀態。
執行緒排程程式從處於同步阻塞狀態的執行緒中選擇一個來獲得鎖,並使得該執行緒進入執行(running)狀態。
五、死鎖及解決方法
from threading import Thread, Lock
import time
mutex_x = Lock()
mutex_y = Lock()
def x_func():
print('X...')
mutex_x.acquire()
print('Lock x something')
time.sleep(1)
mutex_y.acquire()
print('Use y something')
time.sleep(1)
mutex_y.release()
print('x...')
mutex_x.release()
def y_func():
print('Y...')
mutex_y.acquire()
print('Lock y something')
time.sleep(1)
mutex_x.acquire()
print('Use x something')
time.sleep(1)
mutex_x.release()
print('y...')
mutex_y.release()
if __name__ == '__main__':
t1 = Thread(target=x_func)
t2 = Thread(target=y_func)
t1.start()
t2.start()
執行結果:
X...
Y...
Lock x something
Lock y something
上面的程式碼會一直阻塞,程式一直不會結束,因為執行緒1將mutex_x上了鎖,等著鎖mutex_y,與此同時,執行緒2已經將mutex_y上了鎖,等著鎖mutex_x。這樣就形成了死鎖。
線上程間共享多個資源的時候,如果兩個執行緒分別佔有一部分資源並且同時等待對方的資源,就會造成死鎖。
儘管死鎖很少發生,但一旦發生就會造成應用的停止響應。
在程式設計時,要儘量避免死鎖的出現。
為了避免死鎖一直阻塞下去,可以在其中一方新增超時時間,如果超時了,就跳過。
def x_func():
print('X...')
mutex_x.acquire()
print('Lock x something')
time.sleep(1)
result = mutex_y.acquire(timeout=10)
print(result)
print('Use y something')
time.sleep(1)
if result:
mutex_y.release()
print('x...')
mutex_x.release()
執行結果:
X...
Lock x something
Y...
Lock y something
False
Use y something
x...
Use x something
y...
在上面死鎖的x_func中加入超時時間,則超時後死鎖就解開了。
相關文章
- 多執行緒的安全問題及解決方案執行緒
- Python | 多執行緒死鎖問題的巧妙解決方法Python執行緒
- java 執行緒安全問題,解決執行緒安全問題——同步程式碼塊,同步方法,Lock鎖,Object類中wait方法,notify方法。等待喚醒案例。Java執行緒ObjectAI
- 多執行緒併發執行及解決方法執行緒
- Java中解決多執行緒資料安全問題Java執行緒
- 模板方法中的執行緒安全問題執行緒
- 多執行緒併發同步問題及解決方案執行緒
- 03 執行緒安全問題執行緒
- SimpleDateFormat 執行緒安全問題ORM執行緒
- 多執行緒併發安全問題詳解執行緒
- ArrayList 的執行緒安全問題執行緒
- 深入JAVA執行緒安全問題Java執行緒
- 企圖使用c++執行緒解決nodejs單執行緒問題C++執行緒NodeJS
- 多執行緒,你覺得你安全了?(執行緒安全問題)執行緒
- Java多執行緒中執行緒安全與鎖問題Java執行緒
- 深入解讀HashMap執行緒安全性問題HashMap執行緒
- iOS多執行緒全套:執行緒生命週期,多執行緒的四種解決方案,執行緒安全問題,GCD的使用,NSOperation的使用iOS執行緒GC
- parallelStream中的執行緒安全問題Parallel執行緒
- Java學習(28)—(執行緒的控制/生命週期/解決安全問題)Java執行緒
- 集合框架與執行緒安全解決框架執行緒
- 多執行緒問題解釋執行緒
- Java多執行緒:資料一致性問題及解決方案Java執行緒
- lambda中stream執行緒安全的問題執行緒
- 從FMDB執行緒安全問題說起執行緒
- 單例模式執行緒安全reorder問題單例模式執行緒
- Java 執行緒安全問題的本質Java執行緒
- Java—執行緒的生命週期及執行緒控制方法詳解Java執行緒
- 小度分享-【多執行緒工作及執行緒安全】執行緒
- DMAIC如何作為解決問題的方法執行?AI
- SpringMVC中出現的執行緒安全問題分析SpringMVC執行緒
- 5分鐘搞懂多執行緒安全問題執行緒
- 多執行緒的安全性問題(三)執行緒
- JUC之集合中的執行緒安全問題執行緒
- 使用執行緒模擬解決銀行排隊叫號問題執行緒
- Spring中多執行緒的使用及問題Spring執行緒
- java 執行緒淺解02[方法及屬性]Java執行緒
- Python 多執行緒及程式Python執行緒
- Java中列舉的執行緒安全性及序列化問題Java執行緒