开发者社区> 存储 > 正文
0
0
0
18
打赏
0
分享

Python中最常用的五种线程锁,你会用吗?

简介: 对于日常开发者来讲很少会使用到本文的内容,但是对框架作者等是必备知识,同时也是高频的面试常见问题。
+关注继续查看

前言

本文将继续围绕 threading 模块讲解,基本上是纯理论偏多。

对于日常开发者来讲很少会使用到本文的内容,但是对框架作者等是必备知识,同时也是高频的面试常见问题。

官方文档(https://docs.python.org/zh-cn/3.6/library/threading.html)

线程安全

线程安全是多线程或多进程编程中的一个概念,在拥有共享数据的多条线程并行执行的程序中,线程安全的代码会通过同步机制保证各个线程都可以正常且正确的执行,不会出现数据污染等意外情况。

线程安全的问题最主要还是由线程切换导致的,比如一个房间(进程)中有10颗糖(资源),除此之外还有3个小人(1个主线程、2个子线程),当小人A吃了3颗糖后被系统强制进行休息时他认为还剩下7颗糖,而当小人B工作后又吃掉了3颗糖,那么当小人A重新上岗时会认为糖还剩下7颗,但是实际上只有4颗了。

上述例子中线程A和线程B的数据不同步,这就是线程安全问题,它可能导致非常严重的意外情况发生,我们按下面这个示例来进行说明。

下面有一个数值num初始值为0,我们开启2条线程:

  •  线程1对num进行一千万次+1的操作
  •  线程2对num进行一千万次-1的操作

结果可能会令人咋舌,num最后并不是我们所想象的结果0:

  1. import threading  
  2. num = 0  
  3. def add():  
  4.     global num  
  5.     for i in range(10_000_000):  
  6.         num += 1  
  7. def sub():  
  8.     global num  
  9.     for i in range(10_000_000):  
  10.         num -1  
  11. if __name__ == "__main__":  
  12.     subThread01 = threading.Thread(target=add 
  13.     subThread02 = threading.Thread(target=sub 
  14.     subThread01.start()  
  15.     subThread02.start()  
  16.     subThread01.join()  
  17.     subThread02.join()  
  18.     print("num result : %s" % num)  
  19. # 结果三次采集  
  20. # num result : 669214  
  21. # num result : -1849179  
  22. # num result : -525674 

上面这就是一个非常好的案例,想要解决这个问题就必须通过锁来保障线程切换的时机。

需要我们值得留意的是,在Python基本数据类型中list、tuple、dict本身就是属于线程安全的,所以如果有多个线程对这3种容器做操作时,我们不必考虑线程安全问题。

锁的作用

锁是Python提供给我们能够自行操控线程切换的一种手段,使用锁可以让线程的切换变的有序。

一旦线程的切换变的有序后,各个线程之间对数据的访问、修改就变的可控,所以若要保证线程安全,就必须使用锁。

threading模块中提供了5种最常见的锁,下面是按照功能进行划分:

  •  同步锁:lock(一次只能放行一个)
  •  递归锁:rlock(一次只能放行一个)
  •  条件锁:condition(一次可以放行任意个)
  •  事件锁:event(一次全部放行)
  •  信号量锁:semaphore(一次可以放行特定个)

1、Lock() 同步锁

基本介绍

Lock锁的称呼有很多,如:

  •  同步锁
  •  互斥锁

它们是什么意思呢?如下所示:

  1.  互斥指的是某一资源同一时刻仅能有一个访问者对其进行访问,具有唯一性和排他性,但是互斥无法限制访问者对资源的访问顺序,即访问是无序的
  2.  同步是指在互斥的基础上(大多数情况),通过其他机制实现访问者对资源的有序访问
  3.  同步其实已经实现了互斥,是互斥的一种更为复杂的实现,因为它在互斥的基础上实现了有序访问的特点

下面是threading模块与同步锁提供的相关方法:

方法 描述
threading.Lock() 返回一个同步锁对象
lockObject.acquire(blocking=True, timeout=1) 上锁,当一个线程在执行被上锁代码块时,将不允许切换到其他线程运行,默认锁失效时间为1秒
lockObject.release() 解锁,当一个线程在执行未被上锁代码块时,将允许系统根据策略自行切换到其他线程中运行
lockObject.locaked() 判断该锁对象是否处于上锁状态,返回一个布尔值

使用方式

同步锁一次只能放行一个线程,一个被加锁的线程在运行时不会将执行权交出去,只有当该线程被解锁时才会将执行权通过系统调度交由其他线程。

如下所示,使用同步锁解决最上面的问题:

  1. import threading  
  2. num = 0  
  3. def add():  
  4.     lock.acquire()  
  5.     global num  
  6.     for i in range(10_000_000):  
  7.         num += 1  
  8.     lock.release()  
  9. def sub():  
  10.     lock.acquire()  
  11.     global num  
  12.     for i in range(10_000_000):  
  13.         num -1  
  14.     lock.release()  
  15. if __name__ == "__main__":  
  16.     lock = threading.Lock()  
  17.     subThread01 = threading.Thread(target=add 
  18.     subThread02 = threading.Thread(target=sub 
  19.     subThread01.start()  
  20.     subThread02.start()  
  21.     subThread01.join()  
  22.     subThread02.join()  
  23.     print("num result : %s" % num)  
  24. # 结果三次采集  
  25. # num result : 0  
  26. # num result : 0  
  27. # num result : 0 

这样这个代码就完全变成了串行的状态,对于这种计算密集型I/O业务来说,还不如直接使用串行化单线程执行来得快,所以这个例子仅作为一个示例,不能概述锁真正的用途。

死锁现象

对于同步锁来说,一次acquire()必须对应一次release(),不能出现连续重复使用多次acquire()后再重复使用多次release()的操作,这样会引起死锁造成程序的阻塞,完全不动了,如下所示:

  1. import threading  
  2. num = 0  
  3. def add():  
  4.     lock.acquire()  # 上锁  
  5.     lock.acquire()  # 死锁  
  6.     # 不执行  
  7.     global num  
  8.     for i in range(10_000_000):  
  9.         num += 1  
  10.     lock.release()  
  11.     lock.release()  
  12. def sub():  
  13.     lock.acquire()  # 上锁  
  14.     lock.acquire()  # 死锁  
  15.     # 不执行  
  16.     global num  
  17.     for i in range(10_000_000):  
  18.         num -1  
  19.     lock.release()  
  20.     lock.release()  
  21. if __name__ == "__main__":  
  22.     免责声明:本文章版权归属原创作者所有,由本站用户分享仅供学习交流之用!

    参考文档

    Linux下做性能分析:perf

    Google-Wide Profiling: A Continuous Profiling Infrastructure for Data Centers

    Profiling concepts bookmark_border

    What is continuous profiling?

版权声明:本文内容由Webmeng实名注册用户自发贡献,版权归原作者所有,搜寻云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《搜寻云开发者社区用户服务协议》和《Webmeng开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

评论

登录后可评论
相关文章
张力十足!瓜达拉哈拉迎战拉巴斯竞技谁能胜出?
张力十足!瓜达拉哈拉迎战拉巴斯竞技谁能胜出?
3339
0
HBuilderX Wap2App(H5)打包APP去除顶部导航栏
HBuilderX Wap2App(H5)打包APP去除顶部导航栏
3917
0
虚拟主机环境WinWebMail邮件系统的常见问题处理
虚拟主机环境WinWebMail邮件系统的常见问题处理
3570
0
网页js代码禁止鼠标选择和右键功能
网页js代码禁止鼠标选择和右键功能
3677
0
baidu百度搜索单页提交代码
baidu百度搜索单页提交代码
3692
0
Webmeng 安装教程(新手必读)
Webmeng 安装教程(新手必读)
3785
0
webmeng 循环列出数据 循环 列出 数据
webmeng 循环列出数据 循环 列出 数据
3701
0
SEO搜索引擎单页提交代码
SEO搜索引擎单页提交代码
3600
0
数字人是推动元宇宙到来的重要推手
3D打印行业是一个快速增长的行业 ,对熟练专业人员的需求正在增加。3D打印职业主要包括机械工程师、软件开发人员、材料工程师和广泛的业务员,包括销售、营销、客户经理和其他工作岗位
3947
0
在大淘宝技术,前端、后端、算法工程师的日常是什么样的?
适用于XboxSeries S|X 的游戏容量通常都很大,如果你安装了几款类似于《使命召唤:黑色行动冷战》(需要136GB)的游戏,内置的500GB或者1TB固态硬盘会马上被塞满。为了安装更多的游戏,你需要购买一个希捷存储扩展卡。
3788
0
无法做单元化,异地双活也可以玩得很溜
我们经常可以在电商主机中看到10核20线程这样的配置,而且宣传相当于英特尔的酷睿i9,但价格却比i7要便宜得多,下单这样的主机是捡了大便宜吗?
3451
0
库克:苹果的下一站将是印度
在这次会议上,库克还对与游戏开发商 Epic 的诉讼案、薪酬不平、以及苹果未来的计划等问题做出了解答,例如薪酬问题的话,库克和苹果人力高级副总裁迪尔德丽・奥布莱恩 (Deirdre O’Brien) 称,公司会定期评估薪酬实践,确保员工们获得公平的薪酬。
3403
0


+关注
佚名
7
文章
0
问答
0
视频

文章排行榜
最热
最新

相关电子书
更多
基于Lindorm快速构建高效的监控系统
立即下载
Elasticsearch全观测技术解析与应用(构建日志、指标、APM统一观测平台)
立即下载
基于资产配置业务场景下全链路监控平台
立即下载