本想让程序执行加速,却走上了bug翻车之路

本想让程序执行加速,却无意中走上了bug之路

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方法!

The End

期待你关注我的公众号清风Python,如果你觉得不错,希望能动动手指转发给你身边的朋友们。
我的github地址:https://github.com/BreezePython

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值