Python 什么时候使用线程本地内存?

When to use thread local memory in Python?

我刚从 Python 开始,偶然发现了线程本地内存。我写了一个使用线程的小程序:

#!/usr/bin/env python3

import logging
import signal
import threading
import time

class WorkerThread(threading.Thread):
    def __init__(self, idx):
        threading.Thread.__init__(self)
        self.thread_index = idx
        self.thread_alive = True

    def run(self):
        logging.info(f'Thread {self.thread_index} is starting up!')

        while self.thread_alive:
            logging.info(f'Thread {self.thread_index} is still running.')
            time.sleep(1)

        logging.info(f'Thread {self.thread_index} is stopping!')

    def kill(self):
        self.thread_alive = False

def main():
    logging.basicConfig(format = '%(levelname)s: %(message)s', level = logging.INFO)

    def signal_handler(sig, frame):
        logging.info('Ctrl+c pressed, killing threads and shutting down ...')
        nonlocal threads
        for thread in threads:
            thread.kill()

    signal.signal(signal.SIGINT, signal_handler)

    logging.info('Signal handler registered, starting threads ...')

    threads = []
    for i in range(0, 3):
        thread = WorkerThread(i)
        threads.append(thread)
        thread.start()

    for thread in threads:
        thread.join()

    signal.signal(signal.SIGINT, signal.SIG_DFL)

if __name__ == '__main__':
    main()

这个程序按预期工作并打印出如下内容:

> python3 main.py
INFO: Signal handler registered, starting threads ...
INFO: Thread 0 is starting up!
INFO: Thread 0 is still running.
INFO: Thread 1 is starting up!
INFO: Thread 2 is starting up!
INFO: Thread 1 is still running.
INFO: Thread 2 is still running.
INFO: Thread 0 is still running.
INFO: Thread 1 is still running.
INFO: Thread 2 is still running.
INFO: Thread 0 is still running.
INFO: Thread 2 is still running.
INFO: Thread 1 is still running.
INFO: Thread 2 is still running.
INFO: Thread 1 is still running.
INFO: Thread 0 is still running.
INFO: Thread 1 is still running.
INFO: Thread 2 is still running.
INFO: Thread 0 is still running.
^CINFO: Ctrl+c pressed, killing threads and shutting down ...
INFO: Thread 2 is stopping!
INFO: Thread 1 is stopping!
INFO: Thread 0 is stopping!

在这种情况下,thread_indexthread_alive 变量特定于每个线程,因为它们特定于每个对象。但是还有创建thread local memorythreading.local()函数。所以我尝试使用它,因为我希望我的变量是特定于线程的。我在定义 class:

之后使用了它
# imports and shebang

class WorkerThread(threading.Thread):
    thread_index = threading.local()
    thread_alive = threading.local()

# everything else stays the same

但是使用它不会改变任何东西,输出保持不变。所以我的问题是:

threading.local() 适用于您不能或不想修改实现线程的 classes 的情况。

在上面的示例中,您在创建 WorkerThread 和启动线程时处于完全控制状态。因此,您知道每个 运行ning 线程都有一个实例,并且可以将值存储在绑定到线程的实例中。这就是你最初的例子起作用的原因。它在这方面工作正常。

但控制线程并不总是这样。有时线程由库或框架启动,您只提供一些在这些线程中将 运行 的代码。在那种情况下,您不能修改 Thread classes 并向它们添加特定于线程的变量。

让我们举一个多线程网络服务器的例子。您提供应该处理传入请求的功能。您无需创建所有基础设施来监听套接字、解析 http 请求等。所有这些活动都由框架处理。它为您启动一个线程池,当有传入请求时,框架会对其进行解析并使用池中的线程调用您提供的处理程序。

在这种情况下,假设您想为正在处理的请求存储一些上下文(例如当前登录的用户),以便您可以在请求处理期间访问它,但不需要在每个功能明确。您无法将此 currentUser 变量添加到线程 class 中,因为您无法控制它。但是可以用threading.local()来存储。在多个线程中并发处理的请求将拥有自己的副本。

同样适用于您自己的创作。当程序变得更加复杂并且您需要将基础结构代码(管理线程)与应用程序逻辑分开时,您可能不想将线程特定变量添加到线程 classes 并使用 threading.local() 代替。

个人意见:我永远不会在新代码中使用 thread-local 存储。

IMO,线程本地存储的最佳用途是当您有一个旧模块 thread-unaware,它使用全局变量,并且您希望能够从多个线程调用旧模块一些新程序。在那种情况下,简单地用 thread-locals 替换旧模块中的所有全局变量可能是一种让新程序中的每个线程有效地拥有自己的模块“副本”的廉价方法。

但是,如果我正在编写一个 new 模块,用于 multi-threaded 环境,那么模块中将没有全局变量。所有这些状态都将是某些 class(或 classes,)的 成员变量 ,并且调用线程可以各自创建它们自己的实例class(es).