python 多线程锁在更改号码时未按预期工作

python multi-threading lock is not working as expected when doing number change

我正在学习 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_SRS\face_cumstom_dual_cam_0506> cd d:\P_project_SRS\face_cumstom_dual_cam_0506 &ython.exe c:\Users\lijin\.vscode\extensions\ms-python.python-2020.5.80290\pythonFiles\lib\python\debugface_cumstom_dual_cam_0506\threading_producer_consumer.py "
balance [0]
subthread finishes

(face36) D:\P_project_SRS\face_cumstom_dual_cam_0506> cd d:\P_project_SRS\face_cumstom_dual_cam_0506 &ython.exe c:\Users\lijin\.vscode\extensions\ms-python.python-2020.5.80290\pythonFiles\lib\python\debugface_cumstom_dual_cam_0506\threading_producer_consumer.py "
balance [0]
(face36) D:\P_project_SRS\face_cumstom_dual_cam_0506> cd d:\P_project_SRS\face_cumstom_dual_cam_0506 && cmd /C "C:\LeeSRSPrgoramFile\A_anaconda\envs\face36\python.exe c:\Users\lijin\.vscode\extensions\ms-python.python-2020.5.80290\pythonFiles\lib\python\debugpy\no_wheels\debugpy\launcher 2863 -- d:\P_project_SRS\face_cumstom_dual_cam_0506\threading_producer_consumer.py "
balance [0]
subthread finishes

(face36) D:\P_project_SRS\face_cumstom_dual_cam_0506> cd d:\P_project_SRS\face_cumstom_dual_cam_0506 && cmd /C "C:\LeeSRSPrgoramFile\A_anaconda\envs\face36\python.exe c:\Users\lijin\.vscode\extensions\ms-python.python-2020.5.80290\pythonFiles\lib\python\debugpy\no_wheels\debugpy\launcher 2870 -- d:\P_project_SRS\face_cumstom_dual_cam_0506\threading_producer_consumer.py "
balance [0]
subthread finishes

(face36) D:\P_project_SRS\face_cumstom_dual_cam_0506> cd d:\P_project_SRS\face_cumstom_dual_cam_0506 && cmd /C "C:\LeeSRSPrgoramFile\A_anaconda\envs\face36\python.exe c:\Users\lijin\.vscode\extensions\ms-python.python-2020.5.80290\pythonFiles\lib\python\debugpy\no_wheels\debugpy\launcher 3383 -- d:\P_project_SRS\face_cumstom_dual_cam_0506\threading_producer_consumer.py "
balance [0]
subthread finishes

(face36) D:\P_project_SRS\face_cumstom_dual_cam_0506> cd d:\P_project_SRS\face_cumstom_dual_cam_0506 && cmd /C "C:\LeeSRSPrgoramFile\A_anaconda\envs\face36\python.exe c:\Users\lijin\.vscode\extensions\ms-python.python-2020.5.80290\pythonFiles\lib\python\debugpy\no_wheels\debugpy\launcher 3389 -- d:\P_project_SRS\face_cumstom_dual_cam_0506\threading_producer_consumer.py "
balance [0]
subthread finishes

(face36) D:\P_project_SRS\face_cumstom_dual_cam_0506> cd d:\P_project_SRS\face_cumstom_dual_cam_0506 && cmd /C "C:\LeeSRSPrgoramFile\A_anaconda\envs\face36\python.exe c:\Users\lijin\.vscode\extensions\ms-python.python-2020.5.80290\pythonFiles\lib\python\debugpy\no_wheels\debugpy\launcher 3394 -- d:\P_project_SRS\face_cumstom_dual_cam_0506\threading_producer_consumer.py "
balance [0]
subthread finishes

(face36) D:\P_project_SRS\face_cumstom_dual_cam_0506> cd d:\P_project_SRS\face_cumstom_dual_cam_0506 && cmd /C "C:\LeeSRSPrgoramFile\A_anaconda\envs\face36\python.exe c:\Users\lijin\.vscode\extensions\ms-python.python-2020.5.80290\pythonFiles\lib\python\debugpy\no_wheels\debugpy\launcher 3401 -- d:\P_project_SRS\face_cumstom_dual_cam_0506\threading_producer_consumer.py "
balance [0]
subthread finishes

(face36) D:\P_project_SRS\face_cumstom_dual_cam_0506> cd d:\P_project_SRS\face_cumstom_dual_cam_0506 && cmd /C "C:\LeeSRSPrgoramFile\A_anaconda\envs\face36\python.exe c:\Users\lijin\.vscode\extensions\ms-python.python-2020.5.80290\pythonFiles\lib\python\debugpy\no_wheels\debugpy\launcher 3406 -- d:\P_project_SRS\face_cumstom_dual_cam_0506\threading_producer_consumer.py "
balance [0]
subthread finishes

(face36) D:\P_project_SRS\face_cumstom_dual_cam_0506> cd d:\P_project_SRS\face_cumstom_dual_cam_0506 && cmd /C "C:\LeeSRSPrgoramFile\A_anaconda\envs\face36\python.exe c:\Users\lijin\.vscode\extensions\ms-python.python-2020.5.80290\pythonFiles\lib\python\debugpy\no_wheels\debugpy\launcher 3414 -- d:\P_project_SRS\face_cumstom_dual_cam_0506\threading_producer_consumer.py "
balance [12]  --> this is the result should never happened when using a lock.
subthread finishes

我已经尝试了两个 python 语法来启用锁,它们是 with self.threadLock: (请参阅 class 运行 函数中注释掉的部分)和 self.threadLock.acquire(), self.threadLock.release(), 有没有人可以帮忙解释一下这个怪事。

您创建的每个线程都被赋予其 自己的 Lock() 实例,并且它们彼此无关。为了提供互斥,两个线程都必须使用 same Lock 对象。例如,在模块级别创建 Lock 的单个实例,并将其传递给线程的构造函数。

根据@Tim Peters 的建议,我将代码修改如下。

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