热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

python中本地线程ThreadLocal解释和简单自定义示例

前置关于本地线程(线程本地变量,线程本地存

前置

关于本地线程(线程本地变量,线程本地存贮),如果你之前有使用过Flask框架,肯定会好奇,为什么在多线的模式的情况,每个线程都能保留自己一份独立的参数,而不会被其他线程的参数影响,(每个请求上下文中都有自己的内容)。

理论来说对于(单核多线程模式下)Flask Web应用来说,每一个请求就是一个独立的线程。请求之间的信息要完全隔离,避免冲突,这就需要用到Thread Local。ThreadLocal 中每一个变量中都会创建一个副本,每个线程都可以访问自己内部的副本变量。

比如:flask中我们的一个HTTP请求,都能保持独立自己的参数信息,flask中全局变量改为dict, 每一个线程都有对应的自己的key, 并将request作为value存放,

PS:通常我们的flask运行的是,一般有几个模式

  • 单进程单线程
  • 多进程多线程
  • 单进程多线程
  • 单进程多协程

但是在我们的ThreadLocal对于协程的方式是不支持的!后续再梳理一下,在flask中,听闻作者是自己自定义一个Local用于对协程的支持!(比较我们的并发模式有很多种)下我们的Local一些知识点。

PS:python 自带的ThreadLocal只能实现基于线程的并发

关于ThreadLocal

  • 在python中一个ThreadLocal是一个全局变量,但是它是一种特殊的对象,它的状态对线程隔离。

  • 一个线程中的局部变量只有线程自身可以访问,同一个进程下的其他线程不可访问,每个线程都只能读写自己线程的独立副本,这样可以做到线程隔离,做相关数据防污染!这样就可以利用它来保存属于线程自己内部的私有数据。

意思就是:每个线程对一个Thread Local对象的修改都不会影响其他的线程。

ThreadLocal对象实现原理:以线程的ID来保存多份状态字典。

PS:当然也可以改为我们的协程的!指需要内部改为协程的ID!

简单示例:

import threading

# 获取Thread Local对象
local_storage = threading.local()
local_storage.number = 100
local_storage.request = "请求对象"
print('初始化',local_storage.number)
print('初始化',local_storage.request)
# 定义修改变量的线程
class TestThread(threading.Thread):
    def run(self):
        print('开始修改local_storage.number')
        local_storage.number = 10002
        print('开始修改local_storage.request')
        local_storage.request = "我是线程内独立的一个请求对象"
        import time
        time.sleep(1)
        print(local_storage.number)
        print(local_storage.request)
        print(">"*20)

another = TestThread()
another.start()  # 打印2
another.join()
print('在主线程里面的值并没有被修改: ',local_storage.number)  # 但是在主线程里面的值并没有被修改 打印1
print('在主线程里面的值并没有被修改: ',local_storage.request)  # 但是在主线程里面的值并没有被修改 打印1

输出结果为:

初始化 100
初始化 请求对象
开始修改local_storage.number
开始修改local_storage.request
10002
我是线程内独立的一个请求对象
>>>>>>>>>>>>>>>>>>>>
在主线程里面的值并没有被修改:  100
在主线程里面的值并没有被修改:  请求对象

自己模拟顶一个类似的ThreadLocal,之前,关于字节码执行的是,其实我们的了解到过了关于python字典是否是线程安全的问题。

如果验证字典是否是线程的安装的,主要是看原子操作,写操作的是否分多个STORE_SUBSCR字节码。

分析字典的添加是否是线程安全显示 acton() 的反汇编:


storage = {}

def acton():
    global  storage
    storage={"xiaozhong",1121}


import dis

dis.dis(acton)

输出的结果为:

 18           0 LOAD_CONST               1 ('xiaozhong')
              2 LOAD_CONST               2 (1121)
              4 BUILD_SET                2
              6 STORE_GLOBAL             0 (storage)
              8 LOAD_CONST               0 (None)
             10 RETURN_VALUE


字节码指令参考官网:https://docs.python.org/zh-cn/3/library/dis.html
从上面分析看字典设置字典值的时候是线程安全。

  • LOAD_CONST(consti)
    将 co_consts[consti] 推入栈顶。

  • BUILD_SET(count)
    类似于 BUILD_TUPLE 但会创建一个集合。

  • STORE_GLOBAL(namei)
    类似于 STORE_NAME 但会将 name 存储为全局变量。

  • RETURN_VALUE
    返回 TOS 到函数的调用者。

所以我们可以使用字典的形式来模拟:

from _thread import get_ident
import threading
import time


class MyThreadLocal(object):
    def __init__(self):
        # 自定义用于存贮对象的字典
        self.storage = {}
        # 获取当前线程的ID对象
        self.get_ident = get_ident

    #
    def set(self, k, v):
        # 获取到线下的IDident
        ident = self.get_ident()
        # print("线程自己的ID",ident)
        # 判断是否已经存在的
        if ident in self.storage:
            # 存在就创建内部新的字典
            self.storage[ident][k] = v
        else:
            # 不存在就创建,且创建新的内部字典用于存在自己内部的参数变量
            self.storage[ident]={k: v}

    def get(self, k):
        ident = self.get_ident()
        itemobj = self.storage.get(ident)
        if not itemobj:
            return None
        return itemobj.get(k, None)


local_values = MyThreadLocal()


def action_task(num):
    # 没一个线程保存自己的额内部的变量的信息
    local_values.set('name', num)
    # 设置线程内部需要的定义自己的变量
    local_values.set('reques''大爷的%s'%(num))
    time.sleep(2)
    # print(local_values.get('name'), threading.current_thread().name)


for i in range(3):
    # th = threading.Thread(target=task, args=(i,))
    th = threading.Thread(target=action_task, args=(i,), name='线程%s' % i)
    th.start()

print(local_values.__dict__)

输出结果为:

{'storage': {9296: {'name': 0, 'reques': '大爷的0'}, 15760: {'name': 1, 'reques': '大爷的1'}, 12660: {'name': 2, 'reques': '大爷的2'}}, 'get_ident':}

修改使用python魔法函数进行实现,补充几个魔法函数的说明:

class Test:
    def __getattr__(self, key):
        value = self[key]
        if isinstance(value, dict):
            value = Test(value)
        return value

    def __setattr__(self, key, value):
        self.__dict__[key] = value

    def __getitem__(self, item):
        return self.__dict__[item]

    def __setitem__(self, key, value):
        self.__dict__[key] = value


s = Test()
s.name = "后端"# 调用__setattr__ 方法
print(s.name) # 会调用__getattr__方法
s.age = 991   # 调用__setattr__ 方法
print(s.age)
print(s.name) # 会调用__getattr__方法
print(s['age'])  # 调用 __getitem__方法 输出1
s['name'] = 'tom'  # 调用 __setitem__ 方法


修改我们的自定义的本地线程类:参考我们的flask内部自定义的本地线程类的源码

flask内部自定义的本地线程类的源码:

try:
    from greenlet import getcurrent as get_ident
except ImportError:
    try:
        from thread import get_ident
    except ImportError:
        from _thread import get_ident


def release_local(local):
    """Releases the contents of the local for the current context.
    This makes it possible to use locals without a manager.

    Example::

        >>> loc = Local()
        >>> loc.foo = 42
        >>> release_local(loc)
        >>> hasattr(loc, 'foo')
        False

    With this function one can release :class:`Local` objects as well
    as :class:`LocalStack` objects.  However it is not possible to
    release data held by proxies that way, one always has to retain
    a reference to the underlying local object in order to be able
    to release it.

    .. versionadded:: 0.6.1
    "
""
    local.__release_local__()


class Local(object):
    __slots__ = ("__storage__""__ident_func__")

    def __init__(self):
        object.__setattr__(self, "__storage__", {})
        object.__setattr__(self, "__ident_func__", get_ident)

    def __iter__(self):
        return iter(self.__storage__.items())

    def __call__(self, proxy):
        """Create a proxy for a name."""
        return LocalProxy(self, proxy)

    def __release_local__(self):
        self.__storage__.pop(self.__ident_func__(), None)

    def __getattr__(self, name):
        try:
            return self.__storage__[self.__ident_func__()][name]
        except KeyError:
            raise AttributeError(name)

    def __setattr__(self, name, value):
        ident = self.__ident_func__()
        storage = self.__storage__
        try:
            storage[ident][name] = value
        except KeyError:
            storage[ident] = {name: value}

    def __delattr__(self, name):
        try:
            del self.__storage__[self.__ident_func__()][name]
        except KeyError:
            raise AttributeError(name)


我们自己定义的源码:

from _thread import get_ident
import threading
import time


class MyThreadLocal(object):
    def __init__(self):
        # 自定义用于存贮对象的字典
        object.__setattr__(self,'storage',{})
        # self.storage = {}

    def __setattr__(self, key, value):
        # 获取到线下的IDident
        ident = get_ident()
        # print("线程自己的ID",ident)
        # 判断是否已经存在的
        if ident in self.storage:
            # 存在就创建内部新的字典
            self.storage[ident][key] = value
        else:
            # 不存在就创建,且创建新的内部字典用于存在自己内部的参数变量
            self.storage[ident] = {key: value}

    def __getattr__(self, key):
        ident = self.get_ident()
        return self.storage.get(ident)[key]


    def __delattr__(self, key):
        try:
            ident = self.get_ident()
            del self.storage[ident][key]
        except KeyError:
            raise AttributeError(key)




local_values = MyThreadLocal()


def action_task(num):
    # 没一个线程保存自己的额内部的变量的信息
    local_values.name ="你没的%s"%(num)
    local_values.reques = '大爷的%s'%(num)
    # 设置线程内部需要的定义自己的变量
    time.sleep(2)
    # print(local_values.get('name'), threading.current_thread().name)


for i in range(3):
    # th = threading.Thread(target=task, args=(i,))
    th = threading.Thread(target=action_task, args=(i,), name='线程%s' % i)
    th.start()

print(local_values.__dict__)

输出结果:

{'storage': {8200: {'name': '你没的0', 'reques': '大爷的0'}, 16696: {'name': '你没的1', 'reques': '大爷的1'}, 10464: {'name': '你没的2', 'reques': '大爷的2'}}}

总结:Thread通常所谓本地的变量,主要是为每个线程(也可以是协程)内部创建一个字典,字典内部再存储我们的需要定义属于线程自己的内部的变量信息。

个人其他博客地址

简书:https://www.jianshu.com/u/d6960089b087

掘金:https://juejin.cn/user/2963939079225608

小钟同学 | 文 【原创】| QQ:308711822

  • 1:本文相关描述主要是个人的认知和见解,如有不当之处,还望各位大佬指正。
  • 2:关于文章内容,有部分内容参考自互联网整理,如有链接会声明标注;如没有及时标注备注的链接的,如有侵权请联系,我会立即删除处理哟。




推荐阅读
  • Cosmos生态系统为何迅速崛起,波卡作为跨链巨头应如何应对挑战?
    Cosmos生态系统为何迅速崛起,波卡作为跨链巨头应如何应对挑战? ... [详细]
  • MyISAM和InnoDB是MySQL中最为广泛使用的两种存储引擎,每种引擎都有其独特的优势和适用场景。MyISAM引擎以其简单的结构和高效的读取速度著称,适用于以读操作为主、对事务支持要求不高的应用。而InnoDB引擎则以其强大的事务处理能力和行级锁定机制,在需要高并发写操作和数据完整性的场景下表现出色。选择合适的存储引擎应综合考虑业务需求、性能要求和数据一致性等因素。 ... [详细]
  • 在开发过程中,我最初也依赖于功能全面但操作繁琐的集成开发环境(IDE),如Borland Delphi 和 Microsoft Visual Studio。然而,随着对高效开发的追求,我逐渐转向了更加轻量级和灵活的工具组合。通过 CLIfe,我构建了一个高度定制化的开发环境,不仅提高了代码编写效率,还简化了项目管理流程。这一配置结合了多种强大的命令行工具和插件,使我在日常开发中能够更加得心应手。 ... [详细]
  • 如何利用Java 5 Executor框架高效构建和管理线程池
    Java 5 引入了 Executor 框架,为开发人员提供了一种高效管理和构建线程池的方法。该框架通过将任务提交与任务执行分离,简化了多线程编程的复杂性。利用 Executor 框架,开发人员可以更灵活地控制线程的创建、分配和管理,从而提高服务器端应用的性能和响应能力。此外,该框架还提供了多种线程池实现,如固定线程池、缓存线程池和单线程池,以适应不同的应用场景和需求。 ... [详细]
  • 提升Android开发效率:Clean Code的最佳实践与应用
    在Android开发中,提高代码质量和开发效率是至关重要的。本文介绍了如何通过Clean Code的最佳实践来优化Android应用的开发流程。以SQLite数据库操作为例,详细探讨了如何编写高效、可维护的SQL查询语句,并将其结果封装为Java对象。通过遵循这些最佳实践,开发者可以显著提升代码的可读性和可维护性,从而加快开发速度并减少错误。 ... [详细]
  • REST与RPC:选择哪种API架构风格?
    在探讨REST与RPC这两种API架构风格的选择时,本文首先介绍了RPC(远程过程调用)的概念。RPC允许客户端通过网络调用远程服务器上的函数或方法,从而实现分布式系统的功能调用。相比之下,REST(Representational State Transfer)则基于资源的交互模型,通过HTTP协议进行数据传输和操作。本文将详细分析两种架构风格的特点、适用场景及其优缺点,帮助开发者根据具体需求做出合适的选择。 ... [详细]
  • 深入解析HTTPS:保障Web安全的加密协议
    本文详细探讨了HTTPS协议在保障Web安全中的重要作用。首先分析了HTTP协议的不足之处,包括数据传输过程中的安全性问题和内容加密的缺失。接着介绍了HTTPS如何通过使用公钥和私钥的非对称加密技术以及混合加密机制,确保数据的完整性和机密性。最后强调了HTTPS的安全性和可靠性,为现代网络通信提供了坚实的基础。 ... [详细]
  • 本文探讨了如何利用 jQuery 的 JSONP 技术实现跨域调用外部 Web 服务。通过详细解析 JSONP 的工作原理及其在 jQuery 中的应用,本文提供了实用的代码示例和最佳实践,帮助开发者解决跨域请求中的常见问题。 ... [详细]
  • 在日常的项目开发中,测试环境和生产环境通常采用HTTP协议访问服务。然而,从浏览器的角度来看,这种访问方式会被标记为不安全。为了提升安全性,当前大多数生产环境已经转向了HTTPS协议。本文将详细介绍如何在Spring Boot应用中配置SSL证书,以实现HTTPS安全访问。通过这一过程,不仅可以增强数据传输的安全性,还能提高用户对系统的信任度。 ... [详细]
  • 将JavaScript文件嵌入HTML文档是Web开发中的基本操作。常见的方法是通过在HTML文件中使用``标签来引用外部的.js文件。这种方法不仅保持了代码的整洁性,还便于管理和维护。此外,还可以利用模块化脚本和异步加载技术进一步提升页面性能。 ... [详细]
  • 本文深入解析了Java 8并发编程中的`AtomicInteger`类,详细探讨了其源码实现和应用场景。`AtomicInteger`通过硬件级别的原子操作,确保了整型变量在多线程环境下的安全性和高效性,避免了传统加锁方式带来的性能开销。文章不仅剖析了`AtomicInteger`的内部机制,还结合实际案例展示了其在并发编程中的优势和使用技巧。 ... [详细]
  • 在Ubuntu上安装MySQL时解决缺少libaio.so.1错误及libaio在MySQL中的重要性分析
    在Ubuntu系统上安装MySQL时,遇到了缺少libaio.so.1的错误。本文详细介绍了如何解决这一问题,并深入探讨了libaio库在MySQL性能优化中的重要作用。对于初学者而言,理解这些依赖关系和配置步骤是成功安装和运行MySQL的关键。通过本文的指导,读者可以顺利解决相关问题,并更好地掌握MySQL在Linux环境下的部署与管理。 ... [详细]
  • 在Django中提交表单时遇到值错误问题如何解决?
    在Django项目中,当用户提交包含多个选择目标的表单时,可能会遇到值错误问题。本文将探讨如何通过优化表单处理逻辑和验证机制来有效解决这一问题,确保表单数据的准确性和完整性。 ... [详细]
  • 第六章:枚举类型与switch结构的应用分析
    第六章深入探讨了枚举类型与 `switch` 结构在编程中的应用。枚举类型(`enum`)是一种将一组相关常量组织在一起的数据类型,广泛存在于多种编程语言中。例如,在 Cocoa 框架中,处理文本对齐时常用 `NSTextAlignment` 枚举来表示不同的对齐方式。通过结合 `switch` 结构,可以更清晰、高效地实现基于枚举值的逻辑分支,提高代码的可读性和维护性。 ... [详细]
  • ButterKnife 是一款用于 Android 开发的注解库,主要用于简化视图和事件绑定。本文详细介绍了 ButterKnife 的基础用法,包括如何通过注解实现字段和方法的绑定,以及在实际项目中的应用示例。此外,文章还提到了截至 2016 年 4 月 29 日,ButterKnife 的最新版本为 8.0.1,为开发者提供了最新的功能和性能优化。 ... [详细]
author-avatar
king1994
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有