• 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()

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

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


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

更多...

加载中...