并发编程(六)线程安全问题以及lock解决方案

并发编程专栏系列博客

并发编程(一)python并发编程简介

并发编程(二)怎样选择多线程多进程和多协程

并发编程(三)Python编程慢的罪魁祸首。全局解释器锁GIL

并发编程(四)如何使用多线程,使用多线程对爬虫程序进行修改及比较

并发编程(五)python实现生产者消费者模式多线程爬虫

并发编程(六)线程安全问题以及lock解决方案

并发编程(七)好用的线程池ThreadPoolExecutor

并发编程(八)在web服务中使用线程池加速

并发编程(九)使用多进程multiprocessing加速程序运行

并发编程(十)在Flask服务中使用进程池加速

扫描二维码关注公众号,回复: 12917809 查看本文章

并发编程(十一)python异步IO实现并发编程

并发编程(十二)使用subprocess启动电脑任意程序(听歌、解压缩、自动下载等等)

 
 

线程安全概念介绍
  • 线程安全是指某个函数、函数库在多线程环境被调用时,能够正确地处理多个线程之间的共享变量,使程序功能正确完成。

  • 线程不安全是指由于线程的执行随时会发生切换,就造成了不可预料的结果,出现线程不安全。

    # account:银行账户
    # amount取钱金额
    def draw(account, amount):
        if account.balance >= amount:
            account.balance -= amount
    
  • 以上是一个取钱的一个函数方法,那如果将其放在多线程中会出现什么情况?(手机和银行同时取钱~)

  • 假设该账户有1000元,此时两个线程A、B同时执行取钱任务,取800块钱。首先执行线程A判断1000大于800进入if判断,这时突然进行了线程切换,线程B开始执行,也判断1000大于800进入if判断。这时,再次切换到线程A,因为它已经进入if判断所以直接执行取钱就是1000-800。线程A结束后,线程B继续,因为它也已经进入if判断,所以它也会直接减去800,200-800=-600,就会出现bug造成很大的损失。而这种情况就是线程不安全问题,那么,该怎么解决这个问题呢?

Lock用于解决线程安全问题
  • 从上文中我们已经简单了解线程不安全的问题,也遗留了怎么解决线程安全问题的问题。接下来,我们就通过两个方法来解决这个问题。

  • 方法一:try-finally模式

    # 导包
    import threading
    
    # 创建锁
    lock = threading.Lock()
    
    # 上锁
    lock.acquire()
    # 开始执行任务
    try:
    	#do something
        pass
    # 执行完成之后释放锁
    finally:
        lock.release()  
    
  • 方法二:with模式

    # 导包
    import threading
    
    # 创建锁
    lock = threading.Lock()
    
    # 上锁执行任务,完成后释放锁
    with lock:
    	#do something
    
  • 方法二是方法一的简写~

示例
  • 在上文中我们提到了银行取钱的问题,接下来我就以银行取钱的代码为例来进行示例。

  • 首先是不加lock的情况

    # 导包
    import threading
    import time
    
    # 银行账户类
    class Account:
        def __init__(self, balance):
            self.balance = balance
    
    # 取钱方法
    def draw(account, amount):
        if account.balance >= amount:
            # 进行时间睡眠,是为了进行线程切换
            time.sleep(0.1)
            print(threading.current_thread().name, "取钱成功")
            account.balance -= amount
            print(threading.current_thread().name, "余额", account.balance)
        else:
            print(threading.current_thread().name, "取钱失败,余额不足,剩余", account.balance)
    
    
    if __name__ == '__main__':
        account = Account(1000)
        # 创建两个线程进行取钱
        ta = threading.Thread(target=draw, args=(account, 800), name='ta')
        tb = threading.Thread(target=draw, args=(account, 800), name='tb')
    
        ta.start()
        tb.start()
        
    '''
    output:
    tb 取钱成功
    tb 余额 200
    ta 取钱成功
    ta 余额 -600
    '''
    
  • 接下来,我们在上面的程序中修改,加锁

    # 导包
    import threading
    import time
    
    # 创建锁
    lock = threading.Lock()
    
    # 银行账户类
    class Account:
        def __init__(self, balance):
            self.balance = balance
    
    # 取钱函数
    def draw(account, amount):
        # 加锁
        with lock:
            if account.balance >= amount:
                time.sleep(0.1)
                print(threading.current_thread().name, "取钱成功")
                account.balance -= amount
                print(threading.current_thread().name, "余额", account.balance)
            else:
                print(threading.current_thread().name, "取钱失败,余额不足,剩余", account.balance)
    
    
    if __name__ == '__main__':
        account = Account(1000)
        # 创建两个线程进行取钱
        ta = threading.Thread(target=draw, args=(account, 800), name='ta')
        tb = threading.Thread(target=draw, args=(account, 800), name='tb')
    
        ta.start()
        tb.start()
        
        
    '''
    output:
    ta 取钱成功
    ta 余额 200
    tb 取钱失败,余额不足,剩余 200
    '''
    
  • 通过两个代码的比较,你会发现两者就是在取钱函数中加了锁,加了锁以后。ta加锁进入if判断,这是切换线程进入tb,tb由于无法获取锁而不能继续进行取钱任务。这时,再次切换回ta线程,ta完成取钱释放锁。tb获取锁继续执行,判断为余额不足。

猜你喜欢

转载自blog.csdn.net/qq_42546127/article/details/115032193