互斥锁解决 Python 中多线程共享全局变量的问题(推荐)


Posted in Python onSeptember 28, 2020

一、同步概念

同步就是协同步调,按预定的先后次序进行运行。如:你说完,我再说。

"同"字从字面上容易理解为一起动作。

其实不是,在这里,"同"字应是指协同、协助、互相配合。

线程同步,可理解为线程A和B一块配合,A执行到一定程度时要依靠B的某个结果,于是停下来,示意B运行;B执行,再将结果给A;A再继续操作。

之前我们遇到过,如果多个线程共同对某个数据修改,则可能出现不可预料的结果,为了保证数据的正确性,需要对多个线程进行同步。

解决线程同时修改全局变量的方式

我们先把上次那个问题再看下。

import threading
import time

g_num = 0

def work1(num):
 global g_num
 for i in range(num):
  g_num += 1
 print("----in work1, g_num is %d---" % g_num)

def work2(num):
 global g_num
 for i in range(num):
  g_num += 1
 print("----in work2, g_num is %d---" % g_num)

print("---线程创建之前g_num is %d---" % g_num)

t1 = threading.Thread(target=work1, args=(1000000,))
t1.start()

t2 = threading.Thread(target=work2, args=(1000000,))
t2.start()

# 确保子线程都运行结束
while len(threading.enumerate()) != 1:
 time.sleep(1)

print("2个线程对同一个全局变量操作之后的最终结果是:%s" % g_num)

运行结果:

---线程创建之前g_num is 0---
----in work2, g_num is 1048576---
----in work1, g_num is 1155200---
2个线程对同一个全局变量操作之后的最终结果是:1155200

对于这个计算错误的问题,可以通过线程同步来进行解决。

思路,如下:

系统调用 t1,然后获取到 g_num 的值为0,此时上一把锁,即不允许其他线程操作 g_num。

t1 对 g_num 的值进行+1。

t1 解锁,此时 g_num 的值为1,其他的线程就可以使用 g_num 了,而且 g_num 的值不是0而是1。

同理其他线程在对 g_num 进行修改时,都要先上锁,处理完后再解锁,在上锁的整个过程中不允许其他线程访问,就保证了数据的正确性。

思路基本是这个样子,那代码怎么来实现呢?

二、互斥锁解决资源竞争的问题

当多个线程几乎同时修改某一个共享数据的时候,需要进行同步控制。

线程同步能够保证多个线程安全访问竞争资源,最简单的同步机制就是引入互斥锁。

互斥锁为资源引入一个状态:锁定/非锁定。

某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能更改;直到该线程释放资源,将资源的状态变成“非锁定”,其他的线程才能再次锁定该资源。

互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。

互斥锁解决 Python 中多线程共享全局变量的问题(推荐)

threading 模块中定义了 Lock 类,可以方便的处理锁定:

# 创建锁
mutex = threading.Lock()

# 锁定
mutex.acquire()

# 释放
mutex.release()

注意:

如果这个锁之前是没有上锁的,那么 acquire 不会堵塞。

如果在调用 acquire 对这个锁上锁之前,它已经被其他线程上了锁,那么此时 acquire 会堵塞,直到这个锁被解锁为止。

示例:

使用互斥锁完成2个线程对同一个全局变量各加100万次的操作。

import threading
import time

g_num = 0

def test1(num):
 global g_num
 for i in range(num):
  mutex.acquire() # 上锁
  g_num += 1
  mutex.release() # 解锁

 print("---test1---g_num=%d" % g_num)

def test2(num):
 global g_num
 for i in range(num):
  mutex.acquire() # 上锁
  g_num += 1
  mutex.release() # 解锁

 print("---test2---g_num=%d" % g_num)

# 创建一个互斥锁
# 默认是未上锁的状态
mutex = threading.Lock()

# 创建2个线程,让他们各自对g_num加1000000次
p1 = threading.Thread(target=test1, args=(1000000,))
p1.start()

p2 = threading.Thread(target=test2, args=(1000000,))
p2.start()

# 等待计算完成
while len(threading.enumerate()) != 1:
 time.sleep(1)

print("2个线程对同一个全局变量操作之后的最终结果是:%s" % g_num)

运行结果:

---test1---g_num=1989108
---test2---g_num=2000000
2个线程对同一个全局变量操作之后的最终结果是:2000000

可以看到最后的结果,加入互斥锁后,其结果与预期相符。

记住,上锁的代码范围要越小越好。在业务逻辑正确的前提下,能锁一行代码,就不要锁两行。

上锁解锁过程

当一个线程调用锁的 acquire() 方法获得锁时,锁就进入“locked”状态。

每次只有一个线程可以获得锁。

如果此时另一个线程试图获得这个锁,该线程就会变为“blocked”状态,称为“阻塞”,直到拥有锁的线程调用锁的 release() 方法释放锁之后,锁进入“unlocked”状态。

线程调度程序从处于同步阻塞状态的线程中选择一个来获得锁,并使得该线程进入运行(running)状态。

总结

锁的好处:

确保了某段关键代码只能由一个线程从头到尾完整地执行。

锁的坏处:

阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地下降了。

由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方持有的锁时,可能会造成死锁。

到此这篇关于互斥锁解决 Python 中多线程共享全局变量的问题的文章就介绍到这了,更多相关Python 多线程共享全局变量内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Python 相关文章推荐
简单的Apache+FastCGI+Django配置指南
Jul 22 Python
Python中Django 后台自定义表单控件
Mar 28 Python
基于numpy.random.randn()与rand()的区别详解
Apr 17 Python
Python爬虫信息输入及页面的切换方法
May 11 Python
Python使用itchat 功能分析微信好友性别和位置
Aug 05 Python
python打印异常信息的两种实现方式
Dec 24 Python
Python关于__name__属性的含义和作用详解
Feb 19 Python
Python+OpenCV图像处理——实现轮廓发现
Oct 23 Python
jupyter notebook更换皮肤主题的实现
Jan 07 Python
详解Django关于StreamingHttpResponse与FileResponse文件下载的最优方法
Jan 07 Python
Python 中 sorted 如何自定义比较逻辑
Feb 02 Python
python 机器学习的标准化、归一化、正则化、离散化和白化
Apr 16 Python
python 常见的反爬虫策略
Sep 27 #Python
python 5个实用的技巧
Sep 27 #Python
Python日志器使用方法及原理解析
Sep 27 #Python
python 爬取免费简历模板网站的示例
Sep 27 #Python
python如何提升爬虫效率
Sep 27 #Python
python操作链表的示例代码
Sep 27 #Python
python用tkinter实现一个简易能进行随机点名的界面
Sep 27 #Python
You might like
用户的详细注册和判断
2006/10/09 PHP
PHP链表操作简单示例
2016/10/15 PHP
jQuery开发者都需要知道的5个小技巧
2010/01/08 Javascript
让你的博文自动带上缩址的实现代码,方便发到微博客上
2010/12/28 Javascript
33个优秀的jQuery 教程分享(幻灯片、动画菜单)
2011/07/08 Javascript
详解JavaScript中双等号引起的隐性类型转换
2016/05/30 Javascript
AngularJS自定义指令之复制指令实现方法
2017/05/18 Javascript
JavaScript中Require调用js的实例分享
2017/10/27 Javascript
一个Vue页面的内存泄露分析详解
2018/06/25 Javascript
vue 地图可视化 maptalks 篇实例代码详解
2019/05/21 Javascript
如何搭建一个完整的Vue3.0+ts的项目步骤
2020/10/18 Javascript
[00:56]跨越时空加入战场 全新祈求者身心“失落奇艺侍祭”展示
2019/07/20 DOTA
Python3读取文件常用方法实例分析
2015/05/22 Python
Python读写配置文件的方法
2015/06/03 Python
详解Python网络爬虫功能的基本写法
2016/01/28 Python
Django的HttpRequest和HttpResponse对象详解
2018/01/26 Python
Python嵌套列表转一维的方法(压平嵌套列表)
2018/07/03 Python
Python for i in range ()用法详解
2020/09/18 Python
解决Python中报错TypeError: must be str, not bytes问题
2020/04/07 Python
python利用 keyboard 库记录键盘事件
2020/10/16 Python
HTML5的一个显示电池状态的API简介
2015/06/18 HTML / CSS
Html5应用程序缓存(Cache manifest)
2018/06/04 HTML / CSS
澳大利亚最大的女装零售商:Millers
2017/09/10 全球购物
Vision Direct比利时:在线订购隐形眼镜
2019/08/27 全球购物
Sunglass Hut巴西网上商店:男女太阳镜
2020/10/04 全球购物
最新远光软件笔试题面试题内容
2013/11/08 面试题
简历的自荐信
2013/12/19 职场文书
成人继续教育实施方案
2014/03/01 职场文书
建设投标担保书
2014/05/13 职场文书
2014法院四风问题对照检查材料思想汇报
2014/10/04 职场文书
环境卫生整治简报
2015/07/20 职场文书
python基础之匿名函数详解
2021/04/21 Python
CSS3实现的侧滑菜单
2021/04/27 HTML / CSS
虚拟机linux端mysql数据库无法远程访问的解决办法
2021/05/26 MySQL
Mysql数据库group by原理详解
2022/07/07 MySQL
go goth封装第三方认证库示例详解
2022/08/14 Golang