Python | threading02 - 互斥锁解决多个线程之间随机调度,造成“线程不安全”的问题。
文章目录
- 一、前言
- 二、线程不安全的现象
- 2.1、代码
- 2.2、运行
- 三、使用互斥锁解决线程不安全
- 3.1、代码
- 3.2、运行
- 四、忘记释放互斥锁,造成死锁
- 4.1、代码
- 4.2、运行
- 4.3、造成死锁的一种常见案例
- 五、with语句拯救粗心的人类
- 5.1、with语句在互斥锁上的使用
- 5.3、运行的结果
一、前言
python多线程与单片机的RTOS在调度规则完全不一样。python多线程的调度程序会在任何时候中断线程(相当于调度,所以python多线程的调度可以说是很难控制的),单片机的RTOS会有相应的API来产生调度(调度是可控的)。
线程与协程之间的比较还有最后一点要说明:如果使用线程做过重要的编程,就知道写出程序有多么困难,因为调度程序任何时候都可能中断线程。必须记住保留锁,去保护程序中的重要部分,防止多步操作在执行的过程中被中断,防止数据处于无效状态。 —《流畅的Python》- Luciano Ramalho
所以,在我看来:python的协程与单片机的RTOS才是相似的。
—加粗样式
多线程的优势在于并发性,即可以同时运行多个任务。但是当线程需要使用共享数据时,也可能会由于数据不同步产生“错误情况”,这是由系统的线程调度具有一定的随机性造成的。
由于线程之间的任务执行是CPU进行随机调度的,并且每个线程可能只执行了n条指令之后就被切换到别的线程了。当多个线程同时操作一个对象,如果没有很好地保护该对象,会造成程序结果的不可预期,这被称为“线程不安全”。为了保证数据安全,设计了线程锁,即同一时刻只允许一个线程操作该数据。
B站有一个视频讲解线程安全问题,个人觉得不错。
【2021最新版】Python 并发编程实战,用多线程、多进程、多协程加速程序运行
二、线程不安全的现象
2.1、代码
代码的目的是让number累加到2000000(2百万),线程counter_1将number累加到1000000,线程counter_2将number累加到2000000。
# python3.9
import threading
import timenumber = 0
# lock = threading.Lock()def counter_1():"""子线程1,counter_1"""global number # 声明number是全局变量,并不是函数的局部变量# global lock# lock.acquire()for i in range(1000000):number +=1print("子线程%s运算结束后,number = %s" % (threading.current_thread().getName(),number))# lock.release()def counter_2():"""子线程2,counter_2"""global number # 声明number是全局变量,并不是函数的局部变量# global lock# lock.acquire()for i in range(1000000):number +=1print("子线程%s运算结束后,number = %s" % (threading.current_thread().getName(),number))# lock.release()def main():t1 = threading.Thread(target=counter_1,name="counter_1",daemon=True)t2 = threading.Thread(target=counter_2,name="counter_2",daemon=True)t1.start()t2.start()t1.join()t2.join()print("程序运行结束,number =",number)if __name__ == "__main__":main()
2.2、运行
从运行的四次结果看来,每一次的结果都不一样,而且都没有出现一次2000000。这个现象就是因为多个线程同时访问一个对象所造成的线程不安全问题。
三、使用互斥锁解决线程不安全
3.1、代码
# python3.9
import threading
import timenumber = 0
lock = threading.Lock() # 互斥锁def counter_1():"""子线程1,counter_1"""global number # 声明number是全局变量,并不是函数的局部变量global lock # 声明lock是全局变量,并不是函数的局部变量lock.acquire() # 获取互斥锁for i in range(1000000):number +=1print("子线程%s运算结束后,number = %s" % (threading.current_thread().getName(),number))lock.release() # 释放互斥锁(千万记得用完要释放,否则会出现死锁)def counter_2():"""子线程2,counter_2"""global number # 声明number是全局变量,并不是函数的局部变量global lock # 声明lock是全局变量,并不是函数的局部变量lock.acquire() # 获取互斥锁for i in range(1000000):number +=1print("子线程%s运算结束后,number = %s" % (threading.current_thread().getName(),number))lock.release() # 释放互斥锁(千万记得用完要释放,否则会出现死锁)def main():t1 = threading.Thread(target=counter_1,name="counter_1",daemon=True) #创建线程counter_1t2 = threading.Thread(target=counter_2,name="counter_2",daemon=True) #创建线程counter_2t1.start() # 启动线程counter_1t2.start() # 启动线程counter_2t1.join() # 阻塞主线程,等待线程counter_1运行结束t2.join() # 阻塞主线程,等待线程counter_2运行结束print("程序运行结束,number =",number)if __name__ == "__main__":main()
3.2、运行
四次运行的结果都符合预期的2000000,表示互斥锁解决了线程不安全的问题。但是,获取互斥锁并使用完之后一定,一定,一定要释放互斥锁。否则,会出现死锁的问题。(其他获取互斥锁的线程将一直阻塞在那里。)
四、忘记释放互斥锁,造成死锁
4.1、代码
# python3.9
import threading
import timenumber = 0
lock = threading.Lock() # 互斥锁def counter_1():"""子线程1,counter_1"""global number # 声明number是全局变量,并不是函数的局部变量global lock # 声明lock是全局变量,并不是函数的局部变量lock.acquire() # 获取互斥锁for i in range(1000000):number +=1print("子线程%s运算结束后,number = %s" % (threading.current_thread().getName(),number))# lock.release() # 释放互斥锁(千万记得用完要释放,否则会出现死锁)def counter_2():"""子线程2,counter_2"""global number # 声明number是全局变量,并不是函数的局部变量global lock # 声明lock是全局变量,并不是函数的局部变量lock.acquire() # 获取互斥锁for i in range(1000000):number +=1print("子线程%s运算结束后,number = %s" % (threading.current_thread().getName(),number))lock.release() # 释放互斥锁(千万记得用完要释放,否则会出现死锁)def main():t1 = threading.Thread(target=counter_1,name="counter_1",daemon=True) #创建线程counter_1t2 = threading.Thread(target=counter_2,name="counter_2",daemon=True) #创建线程counter_2t1.start() # 启动线程counter_1t2.start() # 启动线程counter_2t1.join() # 阻塞主线程,等待线程counter_1运行结束t2.join() # 阻塞主线程,等待线程counter_2运行结束print("程序运行结束,number =",number)if __name__ == "__main__":main()
在这里我故意忘记将获取的互斥锁释放回去了,会出现什么问题?
4.2、运行
程序卡死在哪里??我在代码上加入一句代码看看程序是不是真的卡在线程counter_2里。
运行代码看看。
运行的结果证明了线程counter_2被阻塞在lock.acquire( )代码这里,并一直一直阻塞下去,造成整个python程序卡死。
所以,紧记要释放互斥锁。
4.3、造成死锁的一种常见案例
为了简化问题,我们设有两个并发的线程( 线程A 和 线程B ),需要 资源1 和 资源2 .假设 线程A 需要 资源1 , 线程B 需要 资源2 .在这种情况下,两个线程都使用各自的锁,目前为止没有冲突。现在假设,在双方释放锁之前, 线程A 需要 资源2 的锁, 线程B 需要 资源1 的锁,没有资源线程不会继续执行。鉴于目前两个资源的锁都是被占用的,而且在对方的锁释放之前都处于等待且不释放锁的状态。这是死锁的典型情况。所以如上所说,使用锁来解决同步问题是一个可行却存在潜在问题的方案。 —摘自《python并行编程中文版》
五、with语句拯救粗心的人类
5.1、with语句在互斥锁上的使用
# python3.9
import threading
import timenumber = 0
lock = threading.Lock() # 互斥锁def counter_1():"""子线程1,counter_1"""global number # 声明number是全局变量,并不是函数的局部变量global lock # 声明lock是全局变量,并不是函数的局部变量# 使用with语句管理互斥锁with lock:# 锁的对象for i in range(1000000):number +=1print("子线程%s运算结束后,number = %s" % (threading.current_thread().getName(),number))def counter_2():"""子线程2,counter_2"""global number # 声明number是全局变量,并不是函数的局部变量global lock # 声明lock是全局变量,并不是函数的局部变量# 使用with语句管理互斥锁with lock:# 锁的对象for i in range(1000000):number +=1print("子线程%s运算结束后,number = %s" % (threading.current_thread().getName(),number))def main():t1 = threading.Thread(target=counter_1,name="counter_1",daemon=True) #创建线程counter_1t2 = threading.Thread(target=counter_2,name="counter_2",daemon=True) #创建线程counter_2t1.start() # 启动线程counter_1t2.start() # 启动线程counter_2t1.join() # 阻塞主线程,等待线程counter_1运行结束t2.join() # 阻塞主线程,等待线程counter_2运行结束print("程序运行结束,number =",number)if __name__ == "__main__":main()
5.3、运行的结果
从运行的结果看来并没有出现线程不安全问题。
with语句真伟大又简洁!!!!
Python | threading02 - 互斥锁解决多个线程之间随机调度,造成“线程不安全”的问题。相关推荐
- Python 进程互斥锁 Lock - Python零基础入门教程
目录 一.Python 线程互斥锁和进程互斥锁 1.创建线程互斥锁 2.创建进程互斥锁 二.进程互斥锁 Lock 函数介绍 三.进程互斥锁 Lock 使用 案例一:使用进程,但不使用互斥锁 案例二:进 ...
- python之互斥锁
python之互斥锁 1.互斥锁的概念 互斥锁: 对共享数据进行锁定,保证同一时刻只能有一个线程去操作. [对共享数据进行锁定可以理解为全局变量] 注意: 互斥锁是多个线程一起去抢,抢到锁的线程先执 ...
- 互斥锁解决缓存雪崩问题(一)
1.创建springboot项目,添加相关依赖 <dependency><groupId>org.springframework.boot</groupId>< ...
- 互斥锁解决缓存雪崩问题(二)
1.创建springboot项目,添加相关依赖 <dependency><groupId>org.springframework.boot</groupId>< ...
- java的尝试性问题_Java并发编程实战 03互斥锁 解决原子性问题
文章系列 摘要 在上一篇文章02Java如何解决可见性和有序性问题当中,我们解决了可见性和有序性的问题,那么还有一个原子性问题咱们还没解决.在第一篇文章01并发编程的Bug源头当中,讲到了把一个或者多 ...
- Python多线程--互斥锁、死锁
1.互斥锁 为解决资源抢夺问题,使用mutex = Threading.Lock()创建锁,使用mutex.acquire()锁定,使用mutex.release()释放锁. 代码一: import ...
- Linux信号量与互斥锁解决生产者与消费者问题
先来看什么是生产者消费者问题: 生产者消费者问题(英语:Producer-consumer problem),也称有限缓冲问题(英语:Bounded-buffer problem),是一个多线程同步问 ...
- Redis( 缓存篇 ==> 互斥锁解决缓存击穿
我们可以在查询缓存未命中的时候添加一个互斥锁.这样一来,在面对高并发的情况下,只有第一个进来的线程才可以拿到锁然后操作数据库,待操作结束后释放锁,未拿到锁的用户则等待一段时间重新查询缓存,直到缓存重建 ...
- Python 多线程总结(2)— 线程锁、线程池、线程数量、互斥锁、死锁、线程同步
主要介绍使用 threading 模块创建线程的 3 种方式,分别为: 创建 Thread 实例函数 创建 Thread 实例可调用的类对象 使用 Thread 派生子类的方式 多线程是提高效率的一种 ...
最新文章
- setcellvalue 格式_POI对EXCEL的操作【重点:如何设置CELL格式为文本格式】
- docker-compose部署prometheus
- 怎么看python环境变量配置是否好了验证图片_python 的 tesserocr 模块安装与获取图片验证码...
- 专访快手传输算法负责人周超博士:LAS标准的推出离不开信念感
- iOS中几种定时器 - 控制了时间,就控制了一切
- python中的命令行参数_python学习笔记6:命令行参数
- 层 数据仓库_小尝试:基于指标体系的数据仓库搭建和数据可视化
- python是面向对象还是过程_python编程:面向对象与过程是什么?
- django+xadmin在线教育平台(十二)
- mysql-索引操作
- ibatis返回数据集映射举例
- 就我不坑2 nyoj(简单模拟)
- 数字电子技术基础阎石老师第五版课后习题解答-很抱歉,其实才写了两道题,大家不要误点进来耽误时间了。但是开始写了又不想删掉,希望日后能补起来吧。
- 前端PDF文件转图片方法
- 天力卓越消息服务器是什么意思,开票版药易通出现RPC服务器不可用是什么问 – 手机爱问...
- php圆周长怎么求,圆的周长怎么求 公式是什么
- Mac电脑的文件快捷访问工具:Default Folder X 5
- HTML和CSS小知识点笔记
- php+dns+缓存,清理电脑dns缓存方法
- 安卓iccid_普通人也可以做码农?安卓手机上这些代码你也可以用