我尝试使用多个线程测试将数据连接/读取到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]
当我运行CCD_ 1时,我期望的结果是0。但实际结果是随机的,有时是-3,有时是9,等等
问题出在哪里?
这可能是一个特性,而不是bug。
为了使结果为0,两个线程都必须按顺序运行。如果你只有两个线程,这可能会起作用。
然而,在第三个线程(主线程(。如果没有额外的措施,就无法判断之后将选择哪个线程运行。
然而,您可以使用例如Barrier
而不是Lock
来强制线程一个接一个地运行。
update
和get
函数是thread-safe
,而add
和sub
函数不是。这将产生同步问题。你还应该对你的add
和test.py
0函数进行线程安全,比如;
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评论的回答(:
让我们检查一下,(请阅读add
函数中的注释(
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没有释放,循环结束(没有任何中断(。最后一次操作将是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):
循环在没有任何中断的情况下立即完成,你会看到正确的结果,但这并不能保证,中断随时可能发生(然后你会看到错误的结果(请多次运行它以查看错误的结果(。
也请看这个。