• Python互斥锁(Lock):解决多线程安全问题

    多线程的优势在于并发性,即可以同时运行多个任务。但是当线程需要使用共享数据时,也可能会由于数据不同步产生“错误情况”,这是由系统的线程调度具有一定的随机性造成的。

    互斥锁的作用就是解决数据不同步问题。关于互斥锁,有一个经典的“银行取钱”问题。银行取钱的基本流程可以分为如下几个步骤:

    1. 用户输入账户、密码,系统判断用户的账户、密码是否匹配。
    2. 用户输入取款金额。
    3. 系统判断账户余额是否大于取款金额。
    4. 如果余额大于取款金额,则取款成功;如果余额小于取款金额,则取款失败。

    乍一看上去,这确实就是日常生活中的取款流程,这个流程没有任何问题。但一旦将这个流程放在多线程并发的场景下,就有可能出现问题。注意,此处说的是有可能,并不是一定。也许你的程序运行了一百万次都没有出现问题,但没有出现问题并不等于没有问题!

    按照上面的流程编写取款程序,并使用两个线程分别模拟两个人使用同一个账户做并发取钱操作。此处忽略检查账户和密码的操作,仅仅模拟后面三步操作。下面先定义一个账户类,该账户类封装了账户编号和余额两个成员变量。

    class Account:
        # 定义构造器
        def __init__(self, account_no, balance):
            # 封装账户编号、账户余额的两个成员变量
            self.account_no = account_no
            self.balance = balance

    接下来程序会定义一个模拟取钱的函数,该函数根据执行账户、取钱数量进行取钱操作,取钱的逻辑是当账户余额不足时无法提取现金,当余额足够时系统吐出钞票,余额减少。

    程序的主程序非常简单,仅仅是创建一个账户,并启动两个线程从该账户中取钱。程序如下:

    import threading
    import time
    import Account
    
    # 定义一个函数来模拟取钱操作
    def draw(account, draw_amount):
        # 账户余额大于取钱数目
        if account.balance >= draw_amount:
            # 吐出钞票
            print(threading.current_thread().name\
                + "取钱成功!吐出钞票:" + str(draw_amount))
    #        time.sleep(0.001)
            # 修改余额
            account.balance -= draw_amount
            print("\t余额为: " + str(account.balance))
        else:
            print(threading.current_thread().name\
                + "取钱失败!余额不足!")
    # 创建一个账户
    acct = Account.Account("1234567" , 1000)
    # 模拟两个线程对同一个账户取钱
    threading.Thread(name='甲', target=draw , args=(acct , 800)).start()
    threading.Thread(name='乙', target=draw , args=(acct , 800)).start()

    先不要管程序中第 12 行被注释掉的代码,上面程序是一个非常简单的取钱逻辑,这个取钱逻辑与实际的取钱操作也很相似。

    多次运行上面程序,很有可能都会看到如图 1 所示的错误结果。


    线程安全问题
    图 1 线程安全问题

更多...

加载中...