我嘗試使用多個執行緒測驗將資料寫入/讀取到 sqlite 資料庫。
有時它似乎沒有得到正確的結果。那是BUG嗎?
我制作了兩個檔案來測驗它。第一個是test.py。
import threading
import master
def add():
for i in range(10):
num = master.get()
tmp = num 1
master.update(tmp)
print(f"add: {i}, {num}")
def sub():
for i in range(10):
num = master.get()
tmp = num - 1
master.update(tmp)
print(f"sub: {i}, {num}")
if __name__ == "__main__":
subThread01 = threading.Thread(target=add)
subThread02 = threading.Thread(target=sub)
subThread01.start()
subThread02.start()
subThread01.join()
subThread02.join()
print(master.get())
第二個檔案是master.py。
import sqlite3
import threading
lock = threading.Lock()
conn = sqlite3.connect(':memory:', check_same_thread=False)
cur = conn.cursor()
# creat table
cur.execute("""CREATE TABLE IF NOT EXISTS info ( userid INT PRIMARY KEY, data INT );""")
conn.commit()
# insert init data
db = (0, 0)
cur.execute("INSERT INTO info VALUES(?, ?);", db)
conn.commit()
# update data
def update(num):
with lock:
db = (num, 0)
cur.execute("UPDATE info set data = ? where userid = ?;", db)
conn.commit()
# get data
def get():
with lock:
cur.execute(f"SELECT data FROM info where userid = 0;")
result = cur.fetchone()
return result[0]
當我運行test.py. 但實際結果是隨機的,有時是 -3,有時是 9,等等。
問題出在哪里?
uj5u.com熱心網友回復:
這可能是一個特性,而不是一個錯誤。
要使結果為 0,則必須安排兩個執行緒完全按順序運行。如果你只有兩個執行緒,那可能會奏效。
但是有第三個執行緒(主執行緒)。如果沒有額外的措施,就無法判斷之后將選擇哪個執行緒運行。
但是,您可以使用例如 aBarrier而不是 aLock來強制執行緒一個接一個地運行。
uj5u.com熱心網友回復:
update和get功能是thread-safe但add和sub功能不是。這將產生同步問題。您還應該對您的add和sub功能進行執行緒安全,例如 ;
def add():
for i in range(10):
with lock:
num = master.get()
tmp = num 1
master.update(tmp)
print(f"add: {i}, {num}")
def sub():
for i in range(10):
with lock:
num = master.get()
tmp = num - 1
master.update(tmp)
print(f"sub: {i}, {num}")
編輯:我的答案不見了,我忘了指定一個新的鎖物件。它應該是這樣的:
import threading
import master
lock=threading.Lock()
def add():
for i in range(10):
with lock:
num = master.get()
tmp = num 1
master.update(tmp)
print(f"add: {i}, {num}")
def sub():
for i in range(10):
with lock:
num = master.get()
tmp = num - 1
master.update(tmp)
print(f"sub: {i}, {num}")
編輯2(作為對OP評論的回答):
讓我們檢查一下,(請閱讀addfunc 中的注釋)
def add():
for i in range(10):
num = master.get() # let's say num==0
tmp = num 1
"""
Now tmp==1. And think that, GIL released and OS switch to subThread02.
When switching, i==0 this is where we left
"""
master.update(tmp)
繼續 subThread02;
def sub():
for i in range(10):
num = master.get()
tmp = num - 1
master.update(tmp)
想想看,GIL 沒有釋放,for 回圈完成(沒有任何中斷)。最后一次操作將是master.update(-10)
最后一次操作后,GIL會釋放,然后作業系統切換到 subThread01。
在add函式中,我們將繼續我們離開的地方,在add函式中, master.update(0)(請注意)將被評估,然后 for 回圈將迭代 9 次,最后它將執行 master.update(10)。所以會出現同步問題并print(master.get())顯示 10,但結果可能會有所不同,可能為 5 或 -3 或可能為 0
您還說“我洗掉sqlite并設定了一個變數,然后我對其進行了測驗,沒有任何同步問題”我希望您在兩個執行緒中都將其更改for i in range(100):為。(因為回圈立即完成而沒有任何中斷,您將看到正確的結果, 但這并不能保證,中斷隨時可能發生)然后你會看到錯誤的結果(請運行多個以查看錯誤的結果)。for i in range(100000):for i in range(100):
請也看看這個。
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/504217.html
