开发者社区> 存储 > 正文
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开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

评论

登录后可评论
相关文章
国内收不到google adsense的PIN可做的操作
国内收不到google adsense的PIN可做的操作
0
远程桌面连接出现了内部错误的问题
远程桌面连接出现了内部错误。我尝试以管理员身份登录,还是无法解决。重启电脑虽然可以暂时解决问题,但错误还是会随机出现。如何轻松地解决Win10远程连接提示内部错误的问题?”
0
新开阿里云服务器必须在安全组放行80,443端口 否则网站打不开
新开阿里云服务器必须在安全组放行80,443端口 否则网站打不开
0
域名解析到的阿里云的网站打不开
Re我的域名解析成功了。但是还是访问不了网站
0
Linux系统内核升级
mainline指由Linus Torvalds亲自制作的内核发布版,是官方当前最新版本的kernel source。在Torvalds对所有其他程序员所做出的重大变化进行整合,并且对先前版本的bug进行几轮修复之后,大约每十周正式发布一个新版本。mainline事实上代表着一个linux kernel分支,这个分支有另一个名称,叫做vanilla。
0
Linux系统yum的安装、升级、卸载命令详解
Linux系统yum的安装、升级、卸载命令详解
0
域名到期后多久可以注册
域名到期后,并不会立即被他人注册,而是经历了一定的释放周期。这是因为域名注册机构为了保护域名的合法权益,会设置一段时间用于处理续期、赎回以及其他相关操作。
0
blob:https格式的视频链接怎么下载
因此这些链接具有一定的时效性,仅在当前会话中有效。 由于 blob URL 是针对浏览器内存中的数据生成的临时链接,直接通过复制链接或其他方式在其他应用程序或设备上访问或下载该视频是不可行的。
0
【宝塔一键部署】项目包构建文档
【宝塔一键部署】项目包构建文档
0
windows云主机挂载磁盘的方法是什么
打开“计算机管理”工具。可以通过右键点击“计算机”图标,然后选择“管理”来打开该工具。
0
Linux更新指令 升级指令
yum命令用于redhat系统下的软件安装和更新,是redhat和Fedora系统中rpm的软件包管理器,使用前要配置好yum源,可以使用极为相似的centos源,而且免费。yum提供了安装,查找,删除软件包的命令,好记又好用。
0


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

文章排行榜
最热
最新

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