本想让程序执行加速,却无意中走上了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




相关文章
|
1月前
|
存储 缓存 Java
程序员血泪史:上线出错后,我做了这三件事儿...
小米,29岁程序员,分享了系统上线遇到的两个问题及其解决方法:一是限售规则错误导致非配置地区也能购买,通过改进匹配逻辑和细化地区限制解决;二是商品详情页信息被误清空,采用深拷贝对象避免直接影响JPA缓存。总结了代码精确匹配、谨慎处理持久化对象及重视用户反馈的重要性。
38 6
|
5月前
|
数据处理 Python
解锁Python多线程编程魔法,告别漫长等待!让数据下载如飞,感受科技带来的速度与激情!
【8月更文挑战第22天】Python以简洁的语法和强大的库支持在多个领域大放异彩。尽管存在全局解释器锁(GIL),Python仍提供多线程支持,尤其适用于I/O密集型任务。通过一个多线程下载数据的例子,展示了如何使用`threading`模块创建多线程程序,并与单线程版本进行了性能对比。实验表明,多线程能显著减少总等待时间,但在CPU密集型任务上GIL可能会限制其性能提升。此案例帮助理解Python多线程的优势及其适用场景。
54 0
|
7月前
|
程序员 C# C++
lpszBlogName C#开发多年中途被迫改行C++但工作中又经常偷偷使用C#的C++程序员
通过AUMID解析出packageFamily,再根据PackageManager解析出安装目录 PackageManager是WinRT的类型,如何在c++中使用WinRT,请参考C++/WinRT 以下代码需要管理员权限才能运行。
|
编解码 前端开发 JavaScript
非科班出身的人想要顺利地转型成为编程领域的专业人士的建议
@[TOC](目录) 非科班想要丝滑转码,可以参考下述步骤: # 1、具体方法 1. 了解想要从事的领域:比如前端开发、数据分析、产品经理等。通过互联网进行调查,了解这些领域的职责、技能需求、发展前景等。 2. 学习基础知识:了解领域后,可以通过互联网、在线课程、书籍等途径学习基础知识。例如,如果想成为前端开发者,需要了解 HTML、CSS 和 JavaScript 等编程语言,掌握前端框架 (如 React、Angular 和 Vue 等) 和版本控制工具 (如 Git 等) 的基本使用。 3. 实践项目:掌握基础知识后,可以尝试实践项目,巩固所学知识并提高技能。例如,可以构建一个
赛博朋克首发Bug多,CDPR:旅程刚开始,已着手更新修复
赛博朋克首发Bug多,CDPR:旅程刚开始,已着手更新修复
330 0
赛博朋克首发Bug多,CDPR:旅程刚开始,已着手更新修复
|
编解码 前端开发 rax
攻城狮都应当知道的——编译器的工作过程
源码要运行,必须先转成二进制的机器码。这是编译器的任务。 比如,下面这段源码(假定文件名叫做test.c)。
206 0
攻城狮都应当知道的——编译器的工作过程
|
算法 程序员 测试技术
面对Bug程序员能做点什么
我们程序不可避免的会出现bug,那么我们能做哪些事情,尽可能减少bug的产生
447 0
面对Bug程序员能做点什么
|
存储 程序员 C++
如果当初学习编程时能有人给我这些忠告该多好
Cecily Carver 是多伦多的一位程序媛,和 Jennie Faber 一起创办了一个游戏制作工作室。她喜欢歌剧、舞蹈和弹钢琴。Cecily 在这篇文章分享她在编程道路上的所感所想,给出很多值得思考的编程箴言以及一些思想误区,比如在你学习编程之前思考一下你的目标、编程不是什么神秘的东西、坚持比方法更重要等,可以让我们在编程路上少走一些弯路,从而有更多的时间学习技术让自己变的越来越强大。
233 0
|
安全 测试技术 程序员
惊心动魄!程序员们说这些时刻再也不想经历了
下面的这个场景你熟悉吗: 在一个月黑风高的晚上,大风无情的刮落着树上的枝叶。一个少年突然从睡梦中惊醒,发现已是一身冷汗,他看了看时间,才凌晨三点多,然后又重新闭上眼睛平复心跳,面无表情地躺在床上一动不动,他要努力着让自己睡着,因为他已经好几天没有睡觉了,他的身体需要好好的休息。
7974 0
瞧!这5000个爱管闲事的工程师干的好事.....
从 2018 年 4 月来到大爱清尘基金开始,师先存就没有正经过过几个周末。
10176 0

相关实验场景

更多