镜像站
数据集
飞天加速计划
天池Notebook
云开发平台
热门
ModelScope
云上运维
云原生
数据库
大数据
AIoT云端一体
云效DevOps
平头哥
钉钉开放平台
全部技术圈
弹性计算
存储服务
容器
serverless
中间件
微服务
可观测
云数据库
PolarDB开源
大数据计算
实时数仓Hologres
实时计算Flink
E-MapReduce
DataWorks
Elasticsearch
机器学习平台PAI
计算机视觉
智能语音交互
自然语言处理
多模态模型
pythonsdk
通用模型
钉钉宜搭
支持服务
码上公益
发布
2021-09-16 14:33:22 3785 北京 举报
本文将继续围绕 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条线程:
结果可能会令人咋舌,num最后并不是我们所想象的结果0:
import threading num = 0 def add(): global num for i in range(10_000_000): num += 1 def sub(): global num for i in range(10_000_000): num -= 1 if __name__ == "__main__": subThread01 = threading.Thread(target=add) subThread02 = threading.Thread(target=sub) subThread01.start() subThread02.start() subThread01.join() subThread02.join() print("num result : %s" % num) # 结果三次采集 # num result : 669214 # num result : -1849179 # num result : -525674
上面这就是一个非常好的案例,想要解决这个问题就必须通过锁来保障线程切换的时机。
需要我们值得留意的是,在Python基本数据类型中list、tuple、dict本身就是属于线程安全的,所以如果有多个线程对这3种容器做操作时,我们不必考虑线程安全问题。
锁是Python提供给我们能够自行操控线程切换的一种手段,使用锁可以让线程的切换变的有序。
一旦线程的切换变的有序后,各个线程之间对数据的访问、修改就变的可控,所以若要保证线程安全,就必须使用锁。
threading模块中提供了5种最常见的锁,下面是按照功能进行划分:
Lock锁的称呼有很多,如:
它们是什么意思呢?如下所示:
下面是threading模块与同步锁提供的相关方法:
同步锁一次只能放行一个线程,一个被加锁的线程在运行时不会将执行权交出去,只有当该线程被解锁时才会将执行权通过系统调度交由其他线程。
如下所示,使用同步锁解决最上面的问题:
import threading num = 0 def add(): lock.acquire() global num for i in range(10_000_000): num += 1 lock.release() def sub(): lock.acquire() global num for i in range(10_000_000): num -= 1 lock.release() if __name__ == "__main__": lock = threading.Lock() subThread01 = threading.Thread(target=add) subThread02 = threading.Thread(target=sub) subThread01.start() subThread02.start() subThread01.join() subThread02.join() print("num result : %s" % num) # 结果三次采集 # num result : 0 # num result : 0 # num result : 0
这样这个代码就完全变成了串行的状态,对于这种计算密集型I/O业务来说,还不如直接使用串行化单线程执行来得快,所以这个例子仅作为一个示例,不能概述锁真正的用途。
对于同步锁来说,一次acquire()必须对应一次release(),不能出现连续重复使用多次acquire()后再重复使用多次release()的操作,这样会引起死锁造成程序的阻塞,完全不动了,如下所示:
import threading num = 0 def add(): lock.acquire() # 上锁 lock.acquire() # 死锁 # 不执行 global num for i in range(10_000_000): num += 1 lock.release() lock.release() def sub(): lock.acquire() # 上锁 lock.acquire() # 死锁 # 不执行 global num for i in range(10_000_000): num -= 1 lock.release() lock.release() if __name__ == "__main__": 免责声明:本文章版权归属原创作者所有,由本站用户分享仅供学习交流之用! 参考文档 Linux下做性能分析:perf Google-Wide Profiling: A Continuous Profiling Infrastructure for Data Centers Profiling concepts bookmark_border What is continuous profiling?
Linux下做性能分析:perf
Google-Wide Profiling: A Continuous Profiling Infrastructure for Data Centers
Profiling concepts bookmark_border
What is continuous profiling?
版权声明:本文内容由Webmeng实名注册用户自发贡献,版权归原作者所有,搜寻云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《搜寻云开发者社区用户服务协议》和《Webmeng开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。
评论