我尝试使用多个线程测试将数据写入/读取到SQLite数据库中。
有时,它似乎得不到正确的结果。那是虫子吗?
我制作了两个文件来测试它。第一个是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
时,我预期的结果是0。但实际结果是随机的,有时是-3,有时是9,等等。
问题出在哪里?
2条答案
按热度按时间qyuhtwio1#
这可能是一个功能,而不是一个错误。
为了使结果为0,两个线程必须被调度为按顺序“准确地”运行。如果你只有两条线,那可能行得通。
然而,还有第三个线程(主线程)。如果没有额外的措施,就没有办法知道在那之后将选择哪个线程运行。
但是,您可以使用
Barrier
而不是Lock
来强制线程一个接一个地运行。sqserrrh2#
update
和get
函数是thread-safe
,但add
和sub
函数不是。这会造成同步问题。您还应该执行线程安全的add
和sub
函数,如;编辑:我的答案丢失,我忘记指定一个新的锁对象。它应该是这样的:
编辑2(作为对OP评论的答复):
让我们来看看,(请阅读
add
函数中的评论)继续使用subThread02;
想想看,GIL没有释放,FOR循环结束(没有任何中断)。最后一次操作将是
master.update(-10)
在最后一次操作之后,GIL将被释放,然后操作系统切换到subThread01。
在
add
函数中,我们将从我们离开的地方继续,在add
函数中,master.update(0)
(请注意)将被求值,然后for loop将迭代9次,最后它将执行master.更新(10)。因此将出现同步问题,print(master.get())
将显示10,但结果可能不同,可能是5、-3或0您还说“我删除了
sqlite
并设置了一个变量,然后我测试了它,没有任何同步问题”,我希望您在两个线程中将这个for i in range(100):
更改为for i in range(100000):
。(因为for i in range(100):
循环立即结束,没有任何中断,您将看到正确的结果,但这不能保证,中断随时可能发生),那么您将看到错误的结果(请多次运行它以查看错误的结果)。请也看看this。