Python 多线程锁在执行数字更改时未按预期工作



我现在正在学习python3多线程并尝试测试以下代码。 代码的作用是使用多线程修改名为test_change的函数中的全局数字balance(初始值 = 0(,方法是首先添加然后减去相同的数字,并使用线程锁来确保balance变量一次由一个线程更改。我循环了 100000 次或更多次以检查天气显示的预期值 0,但是,非常令人失望的是,0 并不总是我能得到的答案。这是下面的代码。我正在使用的编辑器是Vscode。

balance = 0
def test_change(n):
global balance
balance += n
balance -= n
# print("in {}, balance is {}".format(threading.current_thread().name, balance))

class MyThread(threading.Thread):
""" self-defined threading class """
def __init__(self, target_fun, fun_args, loop=False):
threading.Thread.__init__(self)
self.target_fun = target_fun
self.fun_args = fun_args
self.loop = loop
self.threadLock = threading.Lock()
def run(self):  # overwrite parent run function
if not self.loop:
self.target_fun(*self.fun_args)
elif self.loop:
# with self.threadLock:  # balance [15]
# for i in range(1000000):
# self.target_fun(self.fun_args)
for i in range(100000):
self.threadLock.acquire()
self.target_fun(self.fun_args)
self.threadLock.release()

nums = [5, 12]
thread_list = []
for i in range(2):  # create 2 thread object
t = MyThread(target_fun=test_change, fun_args=nums[i], loop=True)
thread_list.append(t)
for i in range(len(thread_list)):  # start thread object
thread_list[i].start()
for i in range(len(thread_list)):  # stop main thread till subthread finishes 
thread_list[i].join()  
print("balance [{}]".format(balance))  # expected to be 0 always
print("subthread finishes")

这是显示的部分执行结果,您可以看到最后的输出。

(face36) D:P_project_SRSface_cumstom_dual_cam_0506> cd d:P_project_SRSface_cumstom_dual_cam_0506 &ython.exe c:Userslijin.vscodeextensionsms-python.python-2020.5.80290pythonFileslibpythondebugface_cumstom_dual_cam_0506threading_producer_consumer.py "
balance [0]
subthread finishes
(face36) D:P_project_SRSface_cumstom_dual_cam_0506> cd d:P_project_SRSface_cumstom_dual_cam_0506 &ython.exe c:Userslijin.vscodeextensionsms-python.python-2020.5.80290pythonFileslibpythondebugface_cumstom_dual_cam_0506threading_producer_consumer.py "
balance [0]
(face36) D:P_project_SRSface_cumstom_dual_cam_0506> cd d:P_project_SRSface_cumstom_dual_cam_0506 && cmd /C "C:LeeSRSPrgoramFileA_anacondaenvsface36python.exe c:Userslijin.vscodeextensionsms-python.python-2020.5.80290pythonFileslibpythondebugpyno_wheelsdebugpylauncher 2863 -- d:P_project_SRSface_cumstom_dual_cam_0506threading_producer_consumer.py "
balance [0]
subthread finishes
(face36) D:P_project_SRSface_cumstom_dual_cam_0506> cd d:P_project_SRSface_cumstom_dual_cam_0506 && cmd /C "C:LeeSRSPrgoramFileA_anacondaenvsface36python.exe c:Userslijin.vscodeextensionsms-python.python-2020.5.80290pythonFileslibpythondebugpyno_wheelsdebugpylauncher 2870 -- d:P_project_SRSface_cumstom_dual_cam_0506threading_producer_consumer.py "
balance [0]
subthread finishes
(face36) D:P_project_SRSface_cumstom_dual_cam_0506> cd d:P_project_SRSface_cumstom_dual_cam_0506 && cmd /C "C:LeeSRSPrgoramFileA_anacondaenvsface36python.exe c:Userslijin.vscodeextensionsms-python.python-2020.5.80290pythonFileslibpythondebugpyno_wheelsdebugpylauncher 3383 -- d:P_project_SRSface_cumstom_dual_cam_0506threading_producer_consumer.py "
balance [0]
subthread finishes
(face36) D:P_project_SRSface_cumstom_dual_cam_0506> cd d:P_project_SRSface_cumstom_dual_cam_0506 && cmd /C "C:LeeSRSPrgoramFileA_anacondaenvsface36python.exe c:Userslijin.vscodeextensionsms-python.python-2020.5.80290pythonFileslibpythondebugpyno_wheelsdebugpylauncher 3389 -- d:P_project_SRSface_cumstom_dual_cam_0506threading_producer_consumer.py "
balance [0]
subthread finishes
(face36) D:P_project_SRSface_cumstom_dual_cam_0506> cd d:P_project_SRSface_cumstom_dual_cam_0506 && cmd /C "C:LeeSRSPrgoramFileA_anacondaenvsface36python.exe c:Userslijin.vscodeextensionsms-python.python-2020.5.80290pythonFileslibpythondebugpyno_wheelsdebugpylauncher 3394 -- d:P_project_SRSface_cumstom_dual_cam_0506threading_producer_consumer.py "
balance [0]
subthread finishes
(face36) D:P_project_SRSface_cumstom_dual_cam_0506> cd d:P_project_SRSface_cumstom_dual_cam_0506 && cmd /C "C:LeeSRSPrgoramFileA_anacondaenvsface36python.exe c:Userslijin.vscodeextensionsms-python.python-2020.5.80290pythonFileslibpythondebugpyno_wheelsdebugpylauncher 3401 -- d:P_project_SRSface_cumstom_dual_cam_0506threading_producer_consumer.py "
balance [0]
subthread finishes
(face36) D:P_project_SRSface_cumstom_dual_cam_0506> cd d:P_project_SRSface_cumstom_dual_cam_0506 && cmd /C "C:LeeSRSPrgoramFileA_anacondaenvsface36python.exe c:Userslijin.vscodeextensionsms-python.python-2020.5.80290pythonFileslibpythondebugpyno_wheelsdebugpylauncher 3406 -- d:P_project_SRSface_cumstom_dual_cam_0506threading_producer_consumer.py "
balance [0]
subthread finishes
(face36) D:P_project_SRSface_cumstom_dual_cam_0506> cd d:P_project_SRSface_cumstom_dual_cam_0506 && cmd /C "C:LeeSRSPrgoramFileA_anacondaenvsface36python.exe c:Userslijin.vscodeextensionsms-python.python-2020.5.80290pythonFileslibpythondebugpyno_wheelsdebugpylauncher 3414 -- d:P_project_SRSface_cumstom_dual_cam_0506threading_producer_consumer.py "
balance [12]  --> this is the result should never happened when using a lock.
subthread finishes

我已经尝试了两种 python 语法来启用锁,这些锁with self.threadLock:(请参阅类运行函数中注释掉的部分(和self.threadLock.acquire(), self.threadLock.release(),有没有人可以帮助解释这种奇怪之处。

你创建的每个线程都有自己的Lock()实例,它们彼此无关。若要提供互斥,两个线程必须使用相同的Lock对象。例如,在模块级别创建Lock的单个实例,并将其传递给线程的构造函数。

按照彼得斯的建议@Tim,我修改了代码,如下所示。

import time
import threading
from threading import Thread
balance = 0
def test_change(n):
global balance
balance += n
balance -= n
print("in {}, balance is {}".format(threading.current_thread().name, balance))

class MyThread(threading.Thread):
""" self-defined threading class """
def __init__(self, lock, target_fun, fun_args, loop=False):  # added a shared lock parameter
threading.Thread.__init__(self)
self.target_fun = target_fun
self.fun_args = fun_args
self.loop = loop
self.lock = lock  # this is the shared lock
def run(self):  # overwrite parent run function
if not self.loop:
self.target_fun(*self.fun_args)
elif self.loop:
for i in range(100):
with self.lock:  # "with" after "for" so that both thread change the "balance" variable alternatively
self.target_fun(self.fun_args)
nums = [5, 12]
thread_list = []
thread_muduleLock = threading.Lock()  # added a shared lock
start = time.perf_counter()
for i in range(2):
t = MyThread(thread_muduleLock, target_fun=test_change, fun_args=nums[i], loop=True)  # passing the shared lock to the thread constructor
thread_list.append(t)
for i in range(len(thread_list)):
thread_list[i].start()
for i in range(len(thread_list)):
thread_list[i].join()  
end = time.perf_counter()
total = end - start
print("balance [{}], total time [{}]".format(balance, total))
print("subthread finishes")

以下是执行结果:

in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-7, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
in Thread-6, balance is 0
balance [0], total time [0.1192338]
subthread finishes

除了上面的那些,我尝试比较withacquire(), release()语句之间的效率,发现前者比后者快一点,这是 100 次循环的结果如下。

"""with"""
for i in range(100):  # with after for, 
with self.lock:
self.target_fun(self.fun_args)
###### result ######
balance [0], total time [0.0022132000000000002]
subthread finishes
"""acquire & release """
for i in range(100):
self.lock.acquire()
self.target_fun(self.fun_args)
self.lock.release()
###### result ######
balance [0], total time [0.0035131000000000003]
subthread finishes

最新更新