Python threading Local()函数用法案例详解

 更新时间:2021年09月06日 14:26:05   作者:码农飞哥  
这篇文章主要介绍了Python threading Local()函数用法案例详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下

前言

当多线程访问同一个公共资源时,如果涉及到修改该公共资源的操作就可能会出现由于数据不同步导致的线程安全问题。一般情况下我们可以通过给公共资源加互斥锁的方式来处理该问题。

当然,除非必须将多线程使用的资源设置为公共资源的情况。如果一个资源不需要在多个线程之间共享。我们也可以使用Python threading模块提供的local()方式来避免线程安全问题。
Python threading模块的local()函数跟Java中的ThreadLocal类有诸多类似的地方,感兴趣的小伙伴可以看下Java版的ThreadLoalJava ThreadLocal原理解析以及应用场景分析案例详解

local() 函数是什么?

threading的local()函数主要是用来封装公共资源,使得同一个公共资源在不同线程之间得以隔离。这句话该如何理解呢?举个例子说明下!假设现在有一个大箱子(相当于公共资源),每个人(相当于各个线程)将自己的手机放入这个大箱子里。如果不做任何控制的话,当人们从大箱子中取出手机时极有可能会出现取错的情况(找不到自己当初放入的手机)。而使用local()函数的话,就相当于对这个大箱子进行管理。当每个人放入手机的时候做一个标记(比如在手机上标记所有者的姓名)并隔离放置到箱子中。这样当人们从大箱子中取出手机就能准确的找到自己当初放入的手机。

调用local()函数会生成一个ThreadLocal对象,该对象是所有线程都能访问的,就像上面例子中的大箱子。但是,放入到ThreadLocal对象中的变量则是各个线程所独有的,随便变量名相同,但是指向的值则是完全不同的。

local()函数如何用?

local()函数使用的基本语法是:

import threading

local=threading.local()

第一步就是引入threading模块,第二步就是调用local()函数得到全局的Threadlocal对象。这样说始终是有点干涩,没味道。那么就给代码加点盐吧。还是从那个大箱子说起。

1. 不做标记,不做隔离

第一个示例代码就是所有人将自己的手机放入大箱子里,不做标记,不做隔离。先放入,过一段时间后再取出。

import threading
import time


def set_telephone(telephone):
    global global_telephone
    global_telephone = telephone
    print(threading.current_thread().name + " 放入的手机是", global_telephone)
    time.sleep(1)
    get_telephone()


def get_telephone():
    print(threading.current_thread().name + " 取出的手机是", global_telephone)


if __name__ == '__main__':
    for i in range(3):
        thread = threading.Thread(target=set_telephone, name='学生' + str(i), args=('手机' + str(i),))
        thread.start()

运行结果是:

学生0 放入的手机是 手机0

学生1 放入的手机是 手机1

学生2 放入的手机是 手机2

学生0 取出的手机是 手机2

学生1 取出的手机是 手机2

学生2 取出的手机是 手机2

这里有三个线程,分别模拟学生0,学生1,学生2 将各种的手机赋值给一个全局变量global_telephone(大箱子),然后取全局变量global_telephone中的值。可以看出取出的结果都变成了手机2。这显然没有达到我们的预期结果。这就是不加控制的后果。

2.使用local()函数加以控制

使用local()函数控制的话,就是将全局变量替换成ThreadLoal对象,由他来管理每个线程中的值。

import threading
import time


def set_telephone(telephone):
    local.telephone = telephone
    print(threading.current_thread().name + " 放入的手机是", local.telephone + "\n")
    time.sleep(1)
    get_telephone()


def get_telephone():
    print(threading.current_thread().name + " 取出的手机是", local.telephone + "\n")


if __name__ == '__main__':
    local = threading.local()
    for i in range(3):
        thread = threading.Thread(target=set_telephone, name='学生' + str(i), args=('手机' + str(i),))
        thread.start()

运行结果是:

学生0 放入的手机是 手机0

学生1 放入的手机是 手机1

学生2 放入的手机是 手机2

学生1 取出的手机是 手机1

学生0 取出的手机是 手机0

学生2 取出的手机是 手机2

可以看出每个学生放入的手机和最终取出的手机是一致的。那么threading的local()函数是如何实现这一效果的呢?我们在这里不妨做一个推理。应该是将手机和它的主人做了一层映射关系。根据主人的唯一标识来寻找自己的手机。

3. 模拟实现local()的功能,创建一个箱子

前面我们推测我们需要定义一个全局的字典来存放每个学生各自放入的手机,字典的键是线程ID,值是指定的键值对。示例代码如下:

import threading
import time

global_goods_dict = {}

# {
#     "线程ID":{"telephone":"放入的具体手机"},
#     "线程ID":{"telephone":"放入的具体手机"},
#     "线程ID":{"telephone":"放入的具体手机"}
#
# }

def set_telephone(telephone):
    # 获取线程ID
    thread_id = threading.get_ident()
    global_goods_dict[thread_id] = {}
    global_goods_dict[thread_id]["telephone"] = telephone
    print(threading.current_thread().name + " 放入的手机是", telephone)
    time.sleep(1)
    get_telephone()


def get_telephone():
    thread_id = threading.get_ident()
    print(threading.current_thread().name + " 取出的手机是", global_goods_dict[thread_id]["telephone"])


if __name__ == '__main__':
    for i in range(3):
        thread = threading.Thread(target=set_telephone, name='学生' + str(i), args=('手机' + str(i),))
        thread.start()

运行结果同上,这里定义了一个全局的字典global_goods_dict,字典的键盘是线程ID,这就保证了每个线程只能取到自己设置的数据。字典的值同样是一个字典。这是因为一个线程的要存的值可能不止一个。这里的global_goods_dict[thread_id]["telephone"] = telephone 就等价于上例中的local.telephone = telephone。这样使用虽然能达到效果,但是使用起来还是有点繁琐。那么能不能想local()函数那样使用起来丝滑呢。

4. 简化代码操作,进一步模拟实现local()函数

我们可以将全局的global_goods_dict字典用一个类封装到一个类中。让该类在自动的设置值

class MyBox:
    box = {}

    def __setattr__(self, key, value):
        thread_id = threading.get_ident()
        # 单元格已存在
        if thread_id in MyBox.box:
            MyBox.box[thread_id][key] = value
        else:
            MyBox.box[thread_id] = {key: value}

    def __getattr__(self, item):
        thread_id = threading.get_ident()
        return MyBox.box[thread_id][item]


def set_telephone(telephone):
    myBox.telephone = telephone
    print(threading.current_thread().name + " 放入的手机是", myBox.telephone + "\n")
    time.sleep(1)
    get_telephone()


def get_telephone():
    print(threading.current_thread().name + " 取出的手机是", myBox.telephone + "\n")


if __name__ == '__main__':
    myBox = MyBox()
    for i in range(3):
        thread = threading.Thread(target=set_telephone, name='学生' + str(i), args=('手机' + str(i),))
        thread.start()

运行结果同上。这里通过MyBox类封装了一个名为box的字典。该字典的键是当前线程ID,值是赋值的变量名以及值组成的键值对。当执行set_telephone方法的myBox.telephone = telephone
,实际上会调用MyBox的__setattr__方法,参数key是telephone,参数value是"手机xx"。当调用myBox.telephone时实际上会调用__getattr__方法,传入的参数item是telephone。取值时首先获取当前线程ID。

在这里插入图片描述

在这里插入图片描述

总结

本文从实际例子出发详细介绍了threading模块的local()函数的使用。

到此这篇关于Python threading Local()函数用法案例详解的文章就介绍到这了,更多相关Python threading Local()函数内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 使用python编写一个语音朗读闹钟功能的示例代码

    使用python编写一个语音朗读闹钟功能的示例代码

    这篇文章主要介绍了使用python编写一个语音朗读闹钟,本文通过示例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-07-07
  • Python使用pyodbc访问数据库操作方法详解

    Python使用pyodbc访问数据库操作方法详解

    这篇文章主要介绍了Python使用pyodbc访问数据库操作方法,结合实例形式详细分析了Python基于pyodbc针对数据库的连接、查询、插入、修改、删除等操作技巧与注意事项,需要的朋友可以参考下
    2018-07-07
  • python matplotlib imshow热图坐标替换/映射实例

    python matplotlib imshow热图坐标替换/映射实例

    这篇文章主要介绍了python matplotlib imshow热图坐标替换/映射实例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-03-03
  • 零基础写python爬虫之使用urllib2组件抓取网页内容

    零基础写python爬虫之使用urllib2组件抓取网页内容

    文章详细介绍了在python2.5环境下,如何使用urllib2这个python自带的组件进行抓取指定网页内容的,整个过程记录的非常的详细,也很简单,有需要的朋友可以参考下,写出自己的python爬虫
    2014-11-11
  • python包装和授权学习教程

    python包装和授权学习教程

    包装是指对一个已经存在的对象进行系定义加工,实现授权是包装的一个特性,下面这篇文章主要给大家介绍了关于python包装和授权的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2023-06-06
  • Python深入浅出分析enum枚举类

    Python深入浅出分析enum枚举类

    在python中枚举是一种类(Enum,IntEnum),存放在enum模块中。枚举类型可以给一组标签赋予一组特定的值,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-07-07
  • Python使用pandas导入csv文件内容的示例代码

    Python使用pandas导入csv文件内容的示例代码

    这篇文章主要介绍了Python使用pandas导入csv文件内容,本文结合示例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-12-12
  • 基于Python实现计算纳什均衡的示例详解

    基于Python实现计算纳什均衡的示例详解

    纳什均衡是一种博弈论中的概念,它描述了一种平衡状态,其中每个参与者都不能通过独立改变其决策来提高自己的利益。本文就来用Python中的Nashpy和PuLP实现计算纳什均衡,感兴趣的可以了解一下
    2023-02-02
  • 基于Python制作一个多进制转换工具

    基于Python制作一个多进制转换工具

    这篇文章主要介绍了如何利用Python制作一个多进制转换工具,可以实现2进制、4进制、8进制、10进制、16进制、32进制直接的互转,需要的可以参考一下
    2022-02-02
  • Python爬虫运用正则表达式的方法和优缺点

    Python爬虫运用正则表达式的方法和优缺点

    这篇文章主要给大家介绍了关于Python爬虫运用正则表达式的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用Python具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2019-08-08

最新评论