Python 线程互斥锁 Lock
Posted 猿说编程
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python 线程互斥锁 Lock相关的知识,希望对你有一定的参考价值。
目录
一.前言
在前一篇文章 Python 线程创建和传参 中我们介绍了关于 Python 线程的一些简单函数使用和线程的参数传递,使用多线程可以同时执行多个任务,提高开发效率,但是在实际开发中往往我们会碰到线程同步问题,假如有这样一个场景:对全局变量累加 1000000 次,为了提高效率,我们可以使用多线程完成,示例代码如下:
# !usr/bin/env python
# -*- coding:utf-8 _*-
"""
@Author:猿说编程
@Blog(个人博客地址): www.codersrc.com
@File:Python 线程互斥锁 Lock.py
@Time:2021/04/22 08:00
@Motto:不积跬步无以至千里,不积小流无以成江海,程序人生的精彩需要坚持不懈地积累!
"""
# 导入线程threading模块
import threading
# 声明全局变量
g_num = 0
def my_thread1():
# 声明全局变量
global g_num
# 循环 1000000 次,每次累计加 1
for i in range(0,1000000):
g_num = g_num + 1
def my_thread2():
# 声明全局变量
global g_num
# 循环 1000000 次,每次累计加 1
for i in range(0,1000000):
g_num = g_num + 1
def main(i):
# 声明全局变量
global g_num
# 初始化全局变量,初始值为 0
g_num = 0
# 创建两个线程,对全局变量进行累计加 1
t1 = threading.Thread(target=my_thread1)
t2 = threading.Thread(target=my_thread2)
# 启动线程
t1.start()
t2.start()
# 阻塞函数,等待线程结束
t1.join()
t2.join()
# 获取全局变量的值
print("第%d次计算结果:%d "% (i,g_num))
if __name__ == "__main__":
# 循环4次,调用main函数,计算全局变量的值
for i in range(1,5):
main(i)
\'\'\'
输出结果:
第1次计算结果:1262996
第2次计算结果:1661455
第3次计算结果:1300211
第4次计算结果:1563699
\'\'\'
what ? 这是什么操作??看着代码好像也没问题,两个线程,各自累加 1000000 次,不应该输出是 2000000 次吗?而且调用了 4 次 main 函数,每次输出的结果还不同!!
二.Python 线程共享全局变量
分析下上面的代码:两个线程共享全局变量并执行 for 循环 1000000 ,每次自动加 1 ,我们都知道两个线程都是同时在运行,也就是说两个线程同时在执行 g_num = g_num + 1 操作, 经过我们冷静分析一波,貌似结果还是应该等于 2000000 ,对不对?
首先,我们将上面全局变量自动加 1 的代码分为两步:
第一步:g_num + 1
第二步:将 g_num + 1 的结果赋值给 g_num
由此可见,执行一个完整的自动加 1 过程需要两步,然而线程却是在同时运行,谁也不能保证线程 1 的第一步和第二步执行完成之后才执行线程 2 的第一步和第二步,执行的过程充满随机性,这就是导致每次计算结果不同的原因所在!
举个简单的例子:
假如当前 g_num 值是 100,当线程 1 执行第一步时,cpu 通过计算获得结果 101,并准备把计算的结果 101 赋值给 g_num,然后再传值的过程中,线程 2 突然开始执行了并且执行了第一步,此时 g_num 的值仍未 100,101 还在传递的过程中,还没成功赋值,线程 2 获得计算结果 101 ,并准备传递给 g_num ,经过一来一去这么一折腾,分明做了两次加 1 操作,g_num 结果却是 101 ,误差就由此产生,往往循环次数越多,产生的误差就越大。
三.Python 线程互斥锁
为了避免上述问题,我们可以利用线程互斥锁解决这个问题。那么互斥锁到底是个什么原理呢?互斥锁就好比排队上厕所,一个坑位只能蹲一个人,只有占用坑位的人完事了,另外一个人才能上!
1.创建互斥锁
导入线程模块,通过 threading.Lock 创建互斥锁.
# 导入线程threading模块
import threading
# 创建互斥锁
mutex = threading.Lock()
2.锁定资源/解锁资源
- **acquire **— 锁定资源,此时资源是锁定状态,其他线程无法修改锁定的资源,直到等待锁定的资源释放之后才能操作;
- release — 释放资源,也称为解锁操作,对锁定的资源解锁,解锁之后其他线程可以对资源正常操作;
以上面的代码为列子:想得到正确的结果,可以直接利用互斥锁在全局变量 加 1 之前 锁定资源,然后在计算完成之后释放资源,这样就是一个完整的计算过程,至于应该是哪个线程先执行,无所谓,先到先得,凭本事说话….演示代码如下:
# !usr/bin/env python
# -*- coding:utf-8 _*-
"""
@Author:猿说编程
@Blog(个人博客地址): www.codersrc.com
@File:Python 线程互斥锁 Lock.py
@Time:2021/04/22 08:00
@Motto:不积跬步无以至千里,不积小流无以成江海,程序人生的精彩需要坚持不懈地积累!
"""
# 导入线程threading模块
import threading
# 声明全局变量
g_num = 0
# 创建互斥锁
mutex = threading.Lock()
def my_thread1():
# 声明全局变量
global g_num
# 循环 1000000 次,每次累计加 1
for i in range(0,1000000):
# 锁定资源
mutex.acquire()
g_num = g_num + 1
# 解锁资源
mutex.release()
def my_thread2():
# 声明全局变量
global g_num
# 循环 1000000 次,每次累计加 1
for i in range(0,1000000):
# 锁定资源
mutex.acquire()
g_num = g_num + 1
# 解锁资源
mutex.release()
def main(i):
# 声明全局变量
global g_num
# 初始化全局变量,初始值为 0
g_num = 0
# 创建两个线程,对全局变量进行累计加 1
t1 = threading.Thread(target=my_thread1)
t2 = threading.Thread(target=my_thread2)
# 启动线程
t1.start()
t2.start()
# 阻塞函数,等待线程结束
t1.join()
t2.join()
# 获取全局变量的值
print("第%d次计算结果:%d "% (i,g_num))
if __name__ == "__main__":
# 循环4次,调用main函数,计算全局变量的值
for i in range(1,5):
main(i)
\'\'\'
输出结果:
第1次计算结果:2000000
第2次计算结果:2000000
第3次计算结果:2000000
第4次计算结果:2000000
\'\'\'
由此可见,全局变量计算加上互斥锁之后,不论执行多少次,计算结果都相同。注意:互斥锁一旦锁定之后要记得解锁,否则资源会一直处于锁定状态;
四.Python 线程死锁
1.单个互斥锁的死锁:acquire / release 是成对出现的,互斥锁对资源锁定之后就一定要解锁,否则资源会一直处于锁定状态,其他线程无法修改;就好比上面的代码,任何一个线程没有释放资源 release,程序就会一直处于阻塞状态(在等待资源被释放),不信你可以试一试~
2.多个互斥锁的死锁:在同时操作多个互斥锁的时候一定要格外小心,因为一不小心就容易进入死循环,假如有这样一个场景:boss 让程序员一实现功能一的开发,让程序员二实现功能二的开发,功能开发完成之后一起整合代码!
# !usr/bin/env python
# -*- coding:utf-8 _*-
"""
@Author:猿说编程
@Blog(个人博客地址): www.codersrc.com
@File:Python 线程互斥锁 Lock.py
@Time:2021/04/22 08:00
@Motto:不积跬步无以至千里,不积小流无以成江海,程序人生的精彩需要坚持不懈地积累!
"""
# 导入线程threading模块
import threading
# 导入线程time模块
import time
# 创建互斥锁
mutex_one = threading.Lock()
mutex_two = threading.Lock()
def programmer_thread1():
mutex_one.acquire()
print("我是程序员1,module1开发正式开始,谁也别动我的代码")
time.sleep(2)
# 此时会堵塞,因为这个mutex_two已经被线程programmer_thread2抢先上锁了,等待解锁
mutex_two.acquire()
print("等待程序员2通知我合并代码")
mutex_two.release()
mutex_one.release()
def programmer_thread2():
mutex_two.acquire()
print("我是程序员2,module2开发正式开始,谁也别动我的代码")
time.sleep(2)
# 此时会堵塞,因为这个mutex_one已经被线程programmer_thread1抢先上锁了,等待解锁
mutex_one.acquire()
print("等待程序员1通知我合并代码")
mutex_one.release()
mutex_two.release()
def main():
t1 = threading.Thread(target=programmer_thread1)
t2 = threading.Thread(target=programmer_thread2)
# 启动线程
t1.start()
t2.start()
# 阻塞函数,等待线程结束
t1.join()
t2.join()
# 整合代码结束
print("整合代码结束 ")
if __name__ == "__main__":
main()
\'\'\'
输出结果:
我是程序员1,module1开发正式开始,谁也别动我的代码
我是程序员2,module2开发正式开始,谁也别动我的代码
\'\'\'
分析下上面代码:程序员 1 在等程序员 2 通知,程序员 2 在等程序员 1 通知,两个线程都陷入阻塞中,因为两个线程都在等待对方解锁,这就是死锁!所以在开发中对于死锁的问题还是需要多多注意!
五.重点总结
- 1.线程与线程之间共享全局变量需要设置互斥锁;
- 2.注意在互斥锁操作中 **acquire / release **成对出现,避免造成死锁;
六.猜你喜欢
- Python for 循环
- Python 字符串
- Python 列表 list
- Python 元组 tuple
- Python 字典 dict
- Python 条件推导式
- Python 列表推导式
- Python 字典推导式
- Python 函数声明和调用
- Python 不定长参数 *argc/**kargcs
- Python 匿名函数 lambda
- Python return 逻辑判断表达式
- Python 字符串/列表/元组/字典之间的相互转换
- Python 局部变量和全局变量
- Python type 函数和 isinstance 函数区别
- Python is 和 == 区别
- Python 可变数据类型和不可变数据类型
- Python 浅拷贝和深拷贝
未经允许不得转载:猿说编程 » Python 线程互斥锁 Lock
本文由博客 - 猿说编程 猿说编程 发布!
以上是关于Python 线程互斥锁 Lock的主要内容,如果未能解决你的问题,请参考以下文章