python中的threading
在日常的python开发中,经常会碰到使用threading模块提升程序执行效率的场景。但今天却遇到了一个翻车场景,所以赶紧拿出来分享给大家。
省略掉业务功能,来举一个场景出发的demo吧:
import time def do_something(): time.sleep(1) class PersonShow: def __init__(self): self.current_name = None def show(self, user): self.current_name = user # do something do_something() print(self.current_name) f = PersonShow() for name in ["大黄", "小花", "旺财", "小强"]: f.show(name) output: 大黄 小花 旺财 小强
程序很简单,我们通过实例化PersonShow的类,然后遍历姓名列表,执行show方法时将姓名赋值给current_name,之后在show方法中调用do_something函数,最终打印current_name字段。简直不要太简单的一段代码。
现在,我们觉得for循环依次执行效率太低了,想通过threading的方式进行多线程执行,该如何修改呢?很简单...
from threading import Thread ... for name in ["大黄", "小花", "旺财", "小强"]: t = Thread(target=f.show, args=(name,)) t.start()
我们导入threading模块后,将for循环内部改为多线程执行,so easy啊。但真的没问题么?大家思考一分钟...
问题分析
上面的代码加入多线程后,执行的结果会是什么,大家是否思考过了?
答案是:
output: 小强 小强 小强 小强
为什么会得到这样的结果呢?这其实是一个很经典的多线程问题。
我们在执行do_something函数时,等待了一秒,但一秒对于计算机来说能做的事情太多了。别说四个名字、四十四百个也不够塞牙缝的。
当使用了threading执行代码后,线程1刚刚完成赋值,还在执行do_something时,线程二、三、四已经创建完成并开始执行。这样导致的结果是,线程1的do_something函数执行完成后,current_name已经被线程四修改为了小强,如此依次打印,导致最终的current_name输出均为小强。那么遇到这种问题,该如何解决呢?
线程锁不是万能的
一般遇到这种多个线程存在互相影响的问题时,大家第一时间想到的是通过线程锁解决冲突。
但很多情况下,尤其较为简单的场景时,添加了线程锁,你的多线程就毫无作用了。比如这道题,想添加线程锁,只能在self.current_name = user
这一步操作,等待打印完成之后释放锁。那多线程还有什么存在的意义?此时,我们更多该考虑的是线程安全。
线程安全
谈到线程安全,字面意思比较好理解,尤其针对这道题,就是把current_name独立分配给各自的线程么。但这样应该如何操作呢?让我们现在通过一个最笨的办法解决冲突。
for name in ["大黄", "小花", "旺财", "小强"]: f = PersonShow() t = Thread(target=f.show, args=(name,)) t.start()
我们在每次for循环的时候,都实例化出来一个f的对象不就解决了该问题么?是解决了,但这样要造成大量的空间浪费。那么有什么更好的解决办法呢?
threading.local
threading.local()这个方法的特点用来保存一个全局变量,但是这个全局变量只有在当前线程才能访问,如果在另外一个线程里面再次进行赋值,那么会在另外一个线程单独创建内存空间来存储,也就是说在不同的线程里面赋值 不会覆盖之前的值,因为每个线程里面都有一个单独的空间来保存这个数据,而且这个数据是隔离的,其他线程无法访问。
我们可以通过如下方式使用:
当变量在代码最外层单独定义时:
localVal = threading.local()
而当变量第一在类中时,我们可以将该类继承local,最开始的代码最终可以修改为:
# -*- coding: utf-8 -*- # @Author : 王翔 # @微信号 : King_Uranus # @公众号 : 清风Python # @GitHub : https://github.com/BreezePython # @Date : 2020/09/06 11:50:42 # @Software : PyCharm # @version :Python 3.7.3 # @File : threading_bug.py import time from threading import Thread, local def do_something(): time.sleep(1) # 将该类继承threading.local,实现线程安全 class PersonShow(local): def __init__(self): self.current_name = None def show(self, user): self.current_name = user # do something do_something() print(self.current_name) f = PersonShow() for name in ["大黄", "小花", "旺财", "小强"]: t = Thread(target=f.show, args=(name,)) t.start() output: 大黄 小花 旺财 小强
以后遇到这种多线程执行时的线程安全问题,一定要记得使用threading中的local方法!