代碼中經常會用到隨機的部分,此時需要使用程序自帶的偽隨機數發生器,本文探討python隨機數發生器的線程安全相關內容。
穩定的隨機
random 確定隨機序列的方法有 seed 和 state 兩種
可以使得隨機數發生器以 n 為種子產生隨後的序列
random.seed()
時表明使用當前系統時間作為隨機種子,也就是隨機重置隨機數發生器import random
def get_random_num(tag):
random.seed()
for _ in range(100):
random.random()
print(tag)
print(random.random())
if __name__ == '__main__':
for index in range(5):
get_random_num(index)
輸出
0 0.08855079666960641 1 0.9249561135155114 2 0.847403937717389 3 0.9581127578680636 4 0.3559537092834082
這表明變化的seed條件會產生不同的隨機序列
import random
def get_random_num(tag):
random.seed(7) # 固定隨機種子為 7
for _ in range(100):
random.random()
print(tag)
print(random.random())
if __name__ == '__main__':
for index in range(5):
get_random_num(index)
輸出
0 0.17621772849037032 1 0.17621772849037032 2 0.17621772849037032 3 0.17621772849037032 4 0.17621772849037032
這表明固定的seed會產生相同的隨機序列
random.setstate 可以將隨機數發生狀態設置為特定的某個情況
import random
def get_random_num(tag, state):
random.setstate(state)
for _ in range(100):
random.random()
print(tag)
print(random.random())
if __name__ == '__main__':
cur_state = random.getstate()
for index in range(5):
get_random_num(index, cur_state)
輸出
0 0.7362097058247947 1 0.7362097058247947 2 0.7362097058247947 3 0.7362097058247947 4 0.7362097058247947
表明固定的state會產生相同的隨機序列
我們設計一個稍微復雜一些的多線程隨機數發生的情況
程序會使用單線程和多線程的方法產生隨機數
import threading
import numpy as np
import random
import time
def get_random_num(tag):
random.seed(7)
for _ in range(random.randint(10, 20)):
time.sleep(0.1 * random.random())
random.random()
print(tag, '-', random.random())
if __name__ == '__main__':
print("############ 單線程 ###########")
for index in range(5):
get_random_num(index)
print("############ 多線程 ###########")
thread_list = list()
for index in range(5):
t = threading.Thread(target=get_random_num, args=(index, ))
thread_list.append(t)
t.start()
for t in thread_list:
t.join()
pass
輸出
-> 第一次運行
############ 單線程 ########### 0 - 0.1878710267871435 1 - 0.1878710267871435 2 - 0.1878710267871435 3 - 0.1878710267871435 4 - 0.1878710267871435 ############ 多線程 ########### 3 - 0.07031557615348971 2 - 0.9554680239214713 4 - 0.48806805903541084 1 - 0.4803951046156485 0 - 0.6920567688453093
-> 第二次運行
############ 單線程 ########### 0 - 0.1878710267871435 1 - 0.1878710267871435 2 - 0.1878710267871435 3 - 0.1878710267871435 4 - 0.1878710267871435 ############ 多線程 ########### 4 - 0.125491512495977 3 - 0.4406268683247505 1 - 0.9554680239214713 2 - 0.4803951046156485 0 - 0.6920567688453093
-> 第三次運行
############ 單線程 ########### 0 - 0.1878710267871435 1 - 0.1878710267871435 2 - 0.1878710267871435 3 - 0.1878710267871435 4 - 0.1878710267871435 ############ 多線程 ########### 4 - 0.19060953756680787 3 - 0.48806805903541084 0 - 0.4803951046156485 1 2 – 0.74035122442809410.6920567688453093
可以看到多線程會打亂本來穩定的隨機數發生器序列,產生不再那麼穩定的隨機數
我們將 random.seed 替換為 random.setstate
import threading
import numpy as np
import random
import time
def get_random_num(tag, state):
random.setstate(state)
for _ in range(random.randint(10, 20)):
time.sleep(0.1 * random.random())
random.random()
print(tag, '-', random.random())
if __name__ == '__main__':
cur_state = random.getstate()
print("############ 單線程 ###########")
for index in range(5):
get_random_num(index, cur_state)
print("############ 多線程 ###########")
thread_list = list()
for index in range(5):
t = threading.Thread(target=get_random_num, args=(index, cur_state, ))
thread_list.append(t)
t.start()
for t in thread_list:
t.join()
pass
輸出
############ 單線程 ########### 0 - 0.15376246243379788 1 - 0.15376246243379788 2 - 0.15376246243379788 3 - 0.15376246243379788 4 - 0.15376246243379788 ############ 多線程 ########### 0 - 0.37956604279157746 4 - 0.5552055004170326 2 - 0.40568119200883823 3 - 0.09736679342311894 1 - 0.9874404365309796
可以看到多線程輸出的還是紛亂的隨機數,表明設置狀態還是會受到多線程的干擾
得出綜合結論: python自帶 random 模塊線程不安全
numpy 也存在 seed 和 state 兩種隨機數狀態設定策略
二者固定時也可以確定隨機數發生序列,我們直接進入線程安全實驗
設置和random模塊測試相同的程序,僅替換隨機數產生器為numpy
import threading
import numpy as np
import time
def get_random_num(tag):
np.random.seed(7)
for _ in range(np.random.randint(10, 20)):
time.sleep(0.1 * np.random.random())
np.random.random()
print(tag, '-', np.random.random())
if __name__ == '__main__':
print("############ 單線程 ###########")
for index in range(5):
get_random_num(index)
print("############ 多線程 ###########")
thread_list = list()
for index in range(5):
t = threading.Thread(target=get_random_num, args=(index, ))
thread_list.append(t)
t.start()
for t in thread_list:
t.join()
pass
輸出
############ 單線程 ###########
0 - 0.4677528597449807
1 - 0.4677528597449807
2 - 0.4677528597449807
3 - 0.4677528597449807
4 - 0.4677528597449807
############ 多線程 ###########
2 - 0.7969513574435644
1 - 0.27425859319442125
3 - 0.8767009301816353
0 - 0.8078354592960695
4 - 0.16471665877901287
numpy 的 seed 也沒有抗住多線程測試
import threading
import numpy as np
import time
def get_random_num(tag, state):
np.random.set_state(state)
for _ in range(np.random.randint(10, 20)):
time.sleep(0.1 * np.random.random())
np.random.random()
print(tag, '-', np.random.random())
if __name__ == '__main__':
state = np.random.get_state()
print("############ 單線程 ###########")
for index in range(5):
get_random_num(index, state)
print("############ 多線程 ###########")
thread_list = list()
for index in range(5):
t = threading.Thread(target=get_random_num, args=(index, state, ))
thread_list.append(t)
t.start()
for t in thread_list:
t.join()
pass
輸出
############ 單線程 ###########
0 - 0.08401109992998335
1 - 0.08401109992998335
2 - 0.08401109992998335
3 - 0.08401109992998335
4 - 0.08401109992998335
############ 多線程 ###########
3 - 0.3760344064206267
1 - 0.9126801411121602
4 - 0.3426880186919875
0 - 0.11892388333372905
2 - 0.8831013606778471
仍然不是線程安全
numpy.random.RandomState
RandomState方法之所以解決問題,在於它不僅設置了隨機數發生器的初始狀態,也會生成一個隨機數發生器實例,產生一個獨立的變量生成隨機數
只要不是同一個實例,相互之間就不會產生影響
上代碼:
import threading
import numpy as np
import time
def get_random_num(tag):
rand_obj = np.random.RandomState(7)
for _ in range(rand_obj.randint(10, 20)):
time.sleep(0.1 * rand_obj.random())
rand_obj.random()
print(tag, '-', rand_obj.random())
if __name__ == '__main__':
print("############ 單線程 ###########")
for index in range(5):
get_random_num(index)
print("############ 多線程 ###########")
thread_list = list()
for index in range(5):
t = threading.Thread(target=get_random_num, args=(index, ))
thread_list.append(t)
t.start()
for t in thread_list:
t.join()
pass
輸出
############ 單線程 ###########
0 - 0.4677528597449807
1 - 0.4677528597449807
2 - 0.4677528597449807
3 - 0.4677528597449807
4 - 0.4677528597449807
############ 多線程 ###########
2 - 0.4677528597449807
043 - - 0.46775285974498070.4677528597449807
1 - - 0.46775285974498070.4677528597449807
這裡輸出是亂的,解釋一下,這不是我的筆誤,是因為隨機數完全相同,幾個線程的運行時間相同,就會在同一時間向終端輸出內容,導致輸出有點亂
不過還是可以看出來每個發生器產生的隨機數完全相同,證實了 RandomState 的線程安全性
numpy.random.RandomState
—— YYDS