程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式師世界 >> 編程語言 >  >> 更多編程語言 >> Python

Python - random 和 numpy.random 線程安全

編輯:Python

代碼中經常會用到隨機的部分,此時需要使用程序自帶的偽隨機數發生器,本文探討python隨機數發生器的線程安全相關內容。

對比內容

  • python 原生 random 庫
  • numpy 中 random 包

隨機數安全需求

  • 我們需要隨機數,但是特定條件下需要穩定的隨機
  • 這表示我們需要產生固定的隨機數,在保證算法或程序正常運行的同時保證結果穩定可復現,對於調試程序是否有必要
  • 安全需求為:在多線程情況下仍然可以保證穩定的偽隨機

random

random 確定隨機序列的方法有 seed 和 state 兩種

random.seed(n)

可以使得隨機數發生器以 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(state)

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會產生相同的隨機序列

random.seed 線程安全

我們設計一個稍微復雜一些的多線程隨機數發生的情況

程序會使用單線程和多線程的方法產生隨機數

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.setstate() 線程安全

我們將 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.random

numpy 也存在 seed 和 state 兩種隨機數狀態設定策略

二者固定時也可以確定隨機數發生序列,我們直接進入線程安全實驗

numpy.random.seed 線程安全

設置和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 也沒有抗住多線程測試

numpy.random.set_state(state) 線程安全

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

仍然不是線程安全

問題分析

  • 總結下來,random模塊和numpy模塊的 seed 和 state 系列方法都沒有做到線程安全
  • 事實上setstate 一類的方法和 seed 方法原理相同,都是設置隨機數發生器的初始狀態,問題在於這種設置是全局的
  • 當多線程穿插使用時會打亂這個序列
  • 因此線程安全的隨機數發生器必須做到相互隔離
  • 解決問題的終極方案為 numpy.random.RandomState

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 的線程安全性

結論

  1. seed , state 一類方法可以確定隨機數發生序列,但這種全局配置的隨機數確定序列做不到線程安全
  2. 線程安全需要確定序列的同時創建線程內的隨機數發生器實例,保證線程之間互不影響,才會產生真正的隨機序列
  3. numpy.random.RandomState —— YYDS

參考資料

  • https://blog.csdn.net/zm714981790/article/details/61195490

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式師世界 All Rights Reserved