互斥锁解决 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 相关文章推荐
一个简单的python爬虫程序 爬取豆瓣热度Top100以内的电影信息
Apr 17 Python
Python获取网段内ping通IP的方法
Jan 31 Python
python实现连连看辅助之图像识别延伸
Jul 17 Python
Python 中判断列表是否为空的方法
Nov 24 Python
Python实现i人事自动打卡的示例代码
Jan 09 Python
Python爬虫库BeautifulSoup获取对象(标签)名,属性,内容,注释
Jan 25 Python
Python tkinter布局与按钮间距设置方式
Mar 04 Python
Django import export实现数据库导入导出方式
Apr 03 Python
Django中FilePathField字段的用法
May 21 Python
python安装和pycharm环境搭建设置方法
May 27 Python
python 多线程共享全局变量的优劣
Sep 24 Python
Python爬虫逆向分析某云音乐加密参数的实例分析
Dec 04 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
蝙蝠侠:侠影之谜
2020/03/04 欧美动漫
DC《小丑》11项提名领跑奥斯卡 Netflix成第92届奥斯卡提名最大赢家
2020/04/09 欧美动漫
提问的智慧
2006/10/09 PHP
mysql中存储过程、函数的一些问题
2007/02/14 PHP
php微信开发接入
2016/08/27 PHP
ThinkPHP实现登录退出功能
2017/06/29 PHP
深入理解JavaScript 闭包究竟是什么
2013/04/12 Javascript
js格式化货币数据实现代码
2013/09/04 Javascript
JQuery 传送中文乱码问题的简单解决办法
2016/05/24 Javascript
全面解析Bootstrap中form、navbar的使用方法
2016/05/30 Javascript
谈谈target=_new和_blank的不同之处
2016/10/25 Javascript
微信小程序之ES6与事项助手的功能实现
2016/11/30 Javascript
Javascript中for循环语句的几种写法总结对比
2017/01/23 Javascript
Angular2+国际化方案(ngx-translate)的示例代码
2017/08/23 Javascript
使用MUI框架模拟手机端的下拉刷新和上拉加载功能
2017/09/04 Javascript
vue.js移动数组位置,同时更新视图的方法
2018/03/08 Javascript
使用react实现手机号的数据同步显示功能的示例代码
2018/04/03 Javascript
详解Nodejs mongoose
2018/06/10 NodeJs
解决JavaScript中0.1+0.2不等于0.3问题
2018/10/23 Javascript
解决vue admin element noCache设置无效的问题
2019/11/12 Javascript
jquery实现鼠标悬浮弹出气泡提示框
2020/12/23 jQuery
python实现无证书加密解密实例
2014/10/27 Python
tensorflow实现逻辑回归模型
2018/09/08 Python
django主动抛出403异常的方法详解
2019/01/04 Python
用Python将Excel数据导入到SQL Server的例子
2019/08/24 Python
win10子系统python开发环境准备及kenlm和nltk的使用教程
2019/10/14 Python
python中remove函数的踩坑记录
2021/01/04 Python
浅谈关于html5中图片抛物线运动的一些心得
2018/01/09 HTML / CSS
菲律宾领先的在线时尚商店:Zalora菲律宾
2018/02/08 全球购物
大学生村官事迹材料
2014/01/21 职场文书
如何写好优秀的创业计划书
2014/01/30 职场文书
电气自动化个人求职信范文
2014/02/03 职场文书
索桥的故事教学反思
2014/02/06 职场文书
情人节寄语大全
2014/04/11 职场文书
实习协议书范本
2014/04/22 职场文书
自查自纠工作总结
2014/10/15 职场文书