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

简介: 在日常的python开发中,经常会碰到使用threading模块提升程序执行效率的场景。但今天却遇到了一个翻车场景,所以赶紧拿出来分享给大家。省略掉业务功能,来举一个场景出发的demo吧

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




相关文章
|
6月前
|
运维 JavaScript 程序员
7 行代码搞崩溃 B 站,原因令人唏嘘!
7 行代码搞崩溃 B 站,原因令人唏嘘!
59 2
|
5月前
|
程序员 C# C++
lpszBlogName C#开发多年中途被迫改行C++但工作中又经常偷偷使用C#的C++程序员
通过AUMID解析出packageFamily,再根据PackageManager解析出安装目录 PackageManager是WinRT的类型,如何在c++中使用WinRT,请参考C++/WinRT 以下代码需要管理员权限才能运行。
亚马逊错误代码大全:5000、8000、9000系列及其他常见问题解决方法
这篇文章将介绍亚马逊各种错误代码的意义以及解决方案。
|
消息中间件 监控 架构师
公司花50k挖了一个BAT架构师,写代码上线直接内存溢出,被开除了!
公司花50k挖了一个BAT架构师,写代码上线直接内存溢出,被开除了!
|
运维 程序员 Android开发
程序人生 - 30多岁程序员选什么样的公司可以稳妥地把技术路线走下去?
程序人生 - 30多岁程序员选什么样的公司可以稳妥地把技术路线走下去?
161 0
程序人生 - 30多岁程序员选什么样的公司可以稳妥地把技术路线走下去?
|
存储 Unix 程序员
程序员的自白:我如何让失败项目起死回生,变成价值 270 亿美元的应用程序?
Slack 是颇受欢迎的企业沟通和协作工具,目前有 63 万企业在使用。2014 年初拿到了 4000 多万美元融资之后又完成 1.2 亿美元的融资,其估值达到了 11.2 亿美元。2015 年 2 月,slack 成立一周年日活跃用户就达到 50 万人。2019 年 6 月 20 日,创业公司 Slack 正式登陆纽交所。 这个应用起源于一个几乎已经宣告失败的游戏项目,发展成今天一家价值 270 亿美元的公司实属不易。今天,我们来听听 Flicr 与 Slack 的联合创始人 Stewart Butterfield 的轶闻趣事。
138 0
程序员的自白:我如何让失败项目起死回生,变成价值 270 亿美元的应用程序?
|
机器学习/深度学习 人工智能 安全
我离开谷歌,决定创业后的几点感悟
直到现在,我做每一份工作的时候一开始都会定下两个目标:让自己毕业,然后在我被开掉之前就走人。自然,当我到了“已经毕业”和“是时候走人”的状态的时候,一段旅程就该结束了。3 月 2 日,在我收到“欢迎来到谷歌!”邮件的那天后已经过了 11 年零 2 周,我交出自己的徽章,告别了一个当初没想到会待这么长时间,对它如此了解的组织。
155 0
我离开谷歌,决定创业后的几点感悟
赛博朋克首发Bug多,CDPR:旅程刚开始,已着手更新修复
赛博朋克首发Bug多,CDPR:旅程刚开始,已着手更新修复
318 0
赛博朋克首发Bug多,CDPR:旅程刚开始,已着手更新修复
|
编解码 前端开发 rax
攻城狮都应当知道的——编译器的工作过程
源码要运行,必须先转成二进制的机器码。这是编译器的任务。 比如,下面这段源码(假定文件名叫做test.c)。
197 0
攻城狮都应当知道的——编译器的工作过程
|
移动开发 前端开发 小程序
避开这些坑:面试问你为什么要离开上一家公司?你会怎么回答?
  在进行社招面试时,有一个问题几乎是必问的:你为什么要离开上一家公司?   其实这个问题主要是想试探一下你的核心诉求,并借此预估一下你在本公司工作的稳定性。常见的答案也无非就是这么几种:对薪酬不满意、干得不爽,或者是想换个环境。   我遇到过好几个初次跳槽的求职者给出的答案是:在原来的公司学不到技术。   一听到这个,我就不由得叹口气:今天这一小时又算是交代了,这人基本上没戏。因为根据我的经验,这句话如果由工作不满两年的人说出来,很大概率这是个不会学习不会感恩又特别爱抱怨的人。   避开这些坑:面试问你为什么要离开上一家公司?你会怎么回答?   首先我不是HR,只是个前端工程师。为
298 0