前言
list
是 Python
常用的幾個基本資料型別之一.正常情況下我們會對 list
有增刪改查的操作,顯然易見不會有任何問題.那麼如果我們試著在多執行緒下操作list
會有問題嗎?
多執行緒下的 list
安全 or 不安全? 不安全!
通常我們說的執行緒安全是指標對某個資料結構的所有操作都是執行緒安全,在這種定義下,Python 常用的資料結構 list,dict,str
等都是執行緒不安全的
儘管多執行緒下的 list
是執行緒不安全的,但是在 append
的操作下是它又是執行緒安全的.
如何判斷執行緒安全呢?
對於執行緒安全不安全,我們可以通過極端條件下去復現,從而得出結論。比如說判斷 list
是否執行緒安全
import threading
import time
# 隨意設定 count 的值,值越大錯誤丟擲的越快
count = 1000
l = []
def add():
for i in range(count):
l.append(i)
time.sleep(0.0001)
def remove():
for i in range(count):
l.remove(i)
time.sleep(0.0001)
t1 = threading.Thread(target=add)
t2 = threading.Thread(target=remove)
t1.start()
t2.start()
t1.join()
t2.join()
print(l)
有時候一次執行並不一定就會出錯,多次重試之後會出現類似下面的錯誤
很顯然這種操作方式不具有普適性,如果要是歐氣太強,說不定會一直不出現異常。
那麼出了這種方式,有沒有比較簡單有效的方法嗎?答案是有的
dis
dis 庫是 Python 自帶的一個庫,可以用來分析位元組碼。這裡我們需要有這樣的認識,位元組碼的每一行都是一個原子操作,多執行緒切換就是以原子操作為單位的,如果一個操作需要兩行位元組碼就說明它是執行緒不安全的
remove
這裡我們先看一下上面 list
的 remove
操作
>>> import dis
>>> def test_remove():
... a = [1]
... a.remove(0)
...
>>> dis.dis(test_remove)
2 0 LOAD_CONST 1 (1)
2 BUILD_LIST 1
4 STORE_FAST 0 (a)
3 6 LOAD_FAST 0 (a)
8 LOAD_ATTR 0 (remove)
10 LOAD_CONST 2 (0)
12 CALL_FUNCTION 1
14 POP_TOP
16 LOAD_CONST 0 (None)
18 RETURN_VALUE
從上面不難看出,整個 remove
操作被分成了好幾條指令,這就意味著在多執行緒情況下會出現錯亂的情況,試想一下,如果多執行緒下都去 remove
列表的話,並且不按照順序,很容易出現問題。
append
在最上面我們說到,list
的 append
操作是執行緒安全的,那麼究竟是為什麼呢?我們同樣來用 dis
檢視一下
8 19 LOAD_GLOBAL 0 (a)
22 LOAD_ATTR 2 (append)
25 LOAD_CONST 2 (1)
28 CALL_FUNCTION 1
31 POP_TOP
這裡顯然,append
也是有幾條指令,勢必在多執行緒執行的情況下也會發生交錯,但是對於多執行緒下我們操作 append
, 我們肯定也不會在乎這個時候 list
到順序問題了,所以我們說它的 append
是執行緒安全的
參考
https://stackoverflow.com/questions/6319207/are-lists-thread-safe/19728536#19728536
https://docs.python.org/3/faq/library.html#what-kinds-of-global-value-mutation-are-thread-safe