美文网首页
本想让程序执行加速,却走上了bug翻车之路

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

作者: 清风Python | 来源:发表于2020-09-06 12:43 被阅读0次

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

相关文章

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

    本想让程序执行加速,却无意中走上了bug之路 python中的threading 在日常的python开发中,经常...

  • python的异常方法

    代码中会有bug,报错,异常。想让系统提供更具体的异常信息,帮我们解决问题。想忽略本次异常,让程序继续执行下去。 ...

  • 谁说程序员不会写对联?

    在程序员的世界里 不止能写bug,找bug,改bug, 还能写对联…... 当让程序员写对联的时候, 程序员们是怎...

  • BUG:今天捉迷藏吗?程序员:当然!

    BUG它当然是喜欢和程序员玩捉迷藏这个游戏的,但是程序员却并不是特别喜欢BUG。因为BUG往往意味着测试小哥会找上...

  • 已成大叔

    2004年的七月 我离开了家乡 走上了打工之路 记得那年才有十四五 坐了三天三夜的火车 晕头转向有点走不稳路 本想...

  • JS CORE

    第一章 错误处理: 错误: 程序运行过程中,导致程序无法正常执行的现象(即bug) 现象: 程序一旦出错,默认会报...

  • jQuery1

    第一章 错误处理: 错误: 程序运行过程中,导致程序无法正常执行的现象(即bug) 现象: 程序一旦出错,默认会报...

  • Node.js - 操作MongoDB的那些坑!

    导读: 如果你是刚接触 MongoDB 的小伙伴,这篇文章会让你少走很多改Bug之路!现在项目中一般直接使用 np...

  • 三种bug定位方法

    1、定位bug产生的过程 测试用例的执行,基本上是程序运行过程bug产生的开始,若测试结果与期望结果有出入,即出现...

  • 不归路上的不归人

    明知此路不通 却一门心思往前走 当初的选择没错 错在行径过程 曾经的信誓旦旦 如今的无人问津 本想,人生之路不止一...

网友评论

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

      本文链接:https://www.haomeiwen.com/subject/xclcektx.html