【问题标题】:What is "thread local storage" in Python, and why do I need it?什么是 Python 中的“线程本地存储”,为什么需要它?
【发布时间】:2010-09-11 10:10:21
【问题描述】:

具体来说,在 Python 中,变量如何在线程之间共享?

虽然我之前使用过threading.Thread,但我从未真正理解或看到变量如何共享的示例。它们是在主线程和子线程之间共享还是仅在子线程之间共享?我什么时候需要使用线程本地存储来避免这种共享?

我已经看到很多关于使用锁在线程之间同步访问共享数据的警告,但我还没有看到一个很好的问题示例。

提前致谢!

【问题讨论】:

  • 标题与问题不符。问题是关于线程之间共享变量,标题暗示它专门关于线程本地存储
  • @Casebash:从这个问题的声音中,Mike 读到 TLS 是避免共享数据引起的问题所必需的,但不清楚默认共享哪些数据,与哪些数据共享,以及它是如何共享的。我调整了标题以更好地匹配问题。

标签: python multithreading thread-local


【解决方案1】:

值得一提的是threading.local() 不是单身人士。

您可以在每个线程中使用更多它们。 它不是一个存储。

【讨论】:

    【解决方案2】:

    我在这里可能错了。如果您知道其他情况,请详细说明,因为这将有助于解释为什么需要使用线程 local()。

    这句话似乎不对,没有错:“如果你想原子地修改另一个线程可以访问的任何东西,你必须用锁来保护它。”我认为这句话 -> 有效

    我认为原子操作是不能访问中断的 Python 字节码块。像“running = True”这样的 Python 语句是原子的。在这种情况下,您不需要锁定 CPU 以防止中断(我相信)。 Python 字节码分解不会被线程中断。

    像“threads_running[5] = True”这样的 Python 代码不是原子的。这里有两块 Python 字节码;一个用于取消引用对象的 list() 和另一个字节码块以将值分配给对象,在这种情况下是列表中的“位置”。可以引发中断-->在块

    线程 local() 与“原子”有何关系?这就是为什么该声明似乎误导了我。如果不是,你能解释一下吗?

    【讨论】:

      【解决方案3】:

      在 Python 中,所有东西都是共享的,除了函数局部变量(因为每个函数调用都有自己的一组局部变量,并且线程总是单独的函数调用。)即使那样,只有变量本身(引用的名称to objects) 是函数的局部变量;对象本身总是全局的,任何东西都可以引用它们。 特定线程的Thread 对象在这方面不是特殊对象。如果您将Thread 对象存储在所有线程都可以访问的位置(如全局变量),那么所有线程都可以访问该Thread 对象。如果你想原子地修改另一个线程可以访问的任何东西,你必须用锁来保护它。当然,所有线程都必须共享这个相同的锁,否则它不会很有效。

      如果你想要实际的线程本地存储,那就是threading.local 的用武之地。threading.local 的属性在线程之间不共享;每个线程只看到它自己放置在那里的属性。如果你对它的实现感到好奇,源代码在标准库中的_threading_local.py。

      【讨论】:

      • 您能否详细说明以下句子? “如果你想原子地修改任何你不是在同一个线程中创建的东西,并且没有存储在任何其他线程可以访问的地方,你必须用锁来保护它。”
      • @changyuheng:这里解释一下什么是原子动作:cs.nott.ac.uk/~psznza/G52CON/lecture4.pdf
      • @TomBusby:如果没有其他线程可以访问它,为什么我们需要用锁来保护它,即为什么我们需要使进程原子化?
      • 请你举个简单的例子:“对象本身总是全局的,任何东西都可以引用它们”。通过引用假设您的意思是读取而不是分配/附加?
      • @variable:我认为他的意思是values have no scope
      【解决方案4】:

      考虑以下代码:

      #/usr/bin/env python
      
      from time import sleep
      from random import random
      from threading import Thread, local
      
      data = local()
      
      def bar():
          print("I'm called from", data.v)
      
      def foo():
          bar()
      
      class T(Thread):
          def run(self):
              sleep(random())
              data.v = self.getName()   # Thread-1 and Thread-2 accordingly
              sleep(1)
              foo()
      
       >> T().start(); T().start()
      我是从 Thread-2 调用的
      我是从 Thread-1 调用的 

      这里 threading.local() 被用作将一些数据从 run() 传递到 bar() 而不更改 foo() 接口的快速而肮脏的方法。

      请注意,使用全局变量并不能解决问题:

      #/usr/bin/env python
      
      from time import sleep
      from random import random
      from threading import Thread
      
      def bar():
          global v
          print("I'm called from", v)
      
      def foo():
          bar()
      
      class T(Thread):
          def run(self):
              global v
              sleep(random())
              v = self.getName()   # Thread-1 and Thread-2 accordingly
              sleep(1)
              foo()
      
       >> T().start(); T().start()
      我是从 Thread-2 调用的
      我是从 Thread-2 调用的 

      同时,如果您能负担得起将这些数据作为 foo() 的参数传递 - 这将是一种更优雅且设计良好的方式:

      from threading import Thread
      
      def bar(v):
          print("I'm called from", v)
      
      def foo(v):
          bar(v)
      
      class T(Thread):
          def run(self):
              foo(self.getName())
      

      但这在使用第三方或设计不佳的代码时并不总是可行的。

      【讨论】:

        【解决方案5】:

        您可以使用threading.local() 创建线程本地存储。

        >>> tls = threading.local()
        >>> tls.x = 4 
        >>> tls.x
        4
        

        存储到 tls 的数据对于每个线程都是唯一的,这将有助于确保不会发生意外共享。

        【讨论】:

          【解决方案6】:

          就像在其他所有语言中一样,Python 中的每个线程都可以访问相同的变量。 “主线程”和子线程之间没有区别。

          与 Python 的一个区别是全局解释器锁意味着一次只能有一个线程运行 Python 代码。然而,在同步访问方面,这并没有多大帮助,因为所有常见的抢占问题仍然适用,并且您必须像在其他语言中一样使用线程原语。但是,这确实意味着您需要重新考虑是否使用线程来提高性能。

          【讨论】:

            猜你喜欢
            • 2016-06-12
            • 2010-10-05
            • 1970-01-01
            • 2013-06-05
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2010-12-08
            • 2016-10-25
            相关资源
            最近更新 更多