Python多线程——线程安全“Lock使用”

系统的线程调度具有一定的随机性。

经典问题

银行取钱问题。
从银行取钱的基本流程基本上可以分为如下几个步骤:

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

代码:

import threading
import time

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

def draw(account, draw_amount):
    if account.balance >= draw_amount:
        # 吐出钞票
        print(threading.current_thread().getName() \
              + "取钱成功!吐出钞票:" + str(draw_amount))
        # 修改余额
        account.balance -= draw_amount
        print("\t 余额为:" + str(account.balance))
    else:
        print(threading.current_thread().getName() \
              + "取钱失败!余额不足!")


# 创建一个账户
acct = Account("001", 1000)
# 模拟两个线程对同一个账户取钱
threading.Thread(name='甲', target=draw, args=(acct, 800)).start()
threading.Thread(name='乙', target=draw, args=(acct, 800)).start()

结果,多数情况下为乙线程取钱失败,但是会出现都取钱成功,余额-600的情况。

 为了防止这种错误的出现,就需要用到锁Lock。

acquire(blocking=True, timeout=-1)

请求对Lock或RLock加锁,其中timeout参数指定加锁多少秒。

Lock 和 RLock 的区别如下:

threading.Lock:它是一个基本的锁对象,每次只能锁定一次,其余的锁请求,需等待锁释放后才能获取。
threading.RLock:它代表可重入锁(Reentrant Lock)。对于可重入锁,在同一个线程中可以对它进行多次锁定,也可以多次释放。如果使用 RLock,那么 acquire() 和 release() 方法必须成对出现。如果调用了 n 次 acquire() 加锁,则必须调用 n 次 release() 才能释放锁。

release()

释放锁。

 

在实现线程安全的控制中,比较常用的是RLock。通常使用RLock的代码格式如下:

class X:
    # 定义需要保证线程安全的方法
    def m():
        # 加锁
        self.lock.acquire()
        try:
            #需要保证线程安全的代码
            # ...方法体
        # 使用finally块来保证释放锁
        finally:
            # 修改完成,释放锁
            self.lock.release()

 使用RLock对象来控制线程安全,当加锁和释放锁出现在不同的作用范围时,通常建议使用finally块来确保在必要时释放锁。

通过使用Lock对象可以非常方便地实现线程安全地类,线程安全的类具有如下特征:

  • 该类的对象可以被多个线程安全地访问。
  • 每个线程在调用该对象地任意方法之后,都将得到正确的结果。
  • 每个线程在调用该对象的任意方法之后,该对象都依然保持合理的状态。

总体来说,不可变类总是线程安全的,因为它的对象状态不可改变。把上面银行问题的Account类改为如下形式,它就是线程安全的:

import threading
import time


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

    # 因为账户余额不允许随便修改,所以只为self.__balance提供getter方法
    def getBalance(self):
        return self._balance

    # 提供一个线程安全的draw()方法来完成取钱操作
    def draw(self, draw_amount):
        # 加锁
        self.lock.acquire()
        try:
            # 账户余额大于取钱数目
            if self._balance >= draw_amount:
                # 吐出钞票
                print(threading.current_thread().name \
                      + "取钱成功!吐出钞票:" + str(draw_amount))
                time.sleep(0.001)
                # 修改余额
                self._balance -= draw_amount
                print("\t余额为:" + str(self._balance))
            else:
                print(threading.current_thread().name \
                      + "取钱失败!余额不足!")
        finally:
            # 修改完成,释放锁
            self.lock.release()


def draw(account, draw_amount):
    # 直接调用account对象的draw方法来执行取钱操作
    account.draw(draw_amount)


acct = Account('001', 1000)
# 模拟两个线程对同一个账户取钱
threading.Thread(name='甲', target=draw, args=(acct, 800)).start()
threading.Thread(name='乙', target=draw, args=(acct, 800)).start()

可变类的线程安全是以降低程序的运行效率作为代价的,为了减少线程安全所带来的负面影响,程序可以采用如下策略:

  • 不要对线程安全类的所有方法都进行同步,只对那些会改变竞争资源(竞争资源也就是共享资源)的方法进行同步。例如,上面 Account 类中的 account_no 实例变量就无须同步,所以程序只对 draw() 方法进行了同步控制。
  • 如果可变类有两种运行环境,单线程环境和多线程环境,则应该为该可变类提供两种版本,即线程不安全版本和线程安全版本。在单线程环境中使用钱程不安全版本以保证性能,在多线程环境中使用线程安全版本。

参考:

http://c.biancheng.net/view/2617.html

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值