Sqlite3-的多线程问题得到了意外的结果



我尝试使用多个线程测试将数据连接/读取到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来强制线程一个接一个地运行。

updateget函数是thread-safe,而addsub函数不是。这将产生同步问题。你还应该对你的addtest.py0函数进行线程安全,比如;

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):循环在没有任何中断的情况下立即完成,你会看到正确的结果,但这并不能保证,中断随时可能发生(然后你会看到错误的结果(请多次运行它以查看错误的结果(。

也请看这个。

最新更新