簡體   English   中英

什么是 Python 中的“線程本地存儲”,為什么我需要它?

[英]What is "thread local storage" in Python, and why do I need it?

具體在 Python 中,變量如何在線程之間共享?

盡管在我從未真正理解或看到變量如何共享的示例之前,我曾使用過threading.Thread 它們是在主線程和子線程之間共享還是僅在子線程之間共享? 我什么時候需要使用線程本地存儲來避免這種共享?

我已經看到許多關於通過使用鎖同步訪問線程間共享數據的警告,但我還沒有看到一個很好的問題示例。

提前致謝!

在 Python 中,除了函數局部變量之外,一切都是共享的(因為每個函數調用都有自己的一組局部變量,而線程始終是單獨的函數調用。)即使如此,也只有變量本身(引用對象的名稱)是函數的局部; 對象本身總是全局的,任何東西都可以引用它們。 特定線程的Thread對象在這方面並不是一個特殊的對象。 如果您將Thread對象存儲在所有線程都可以訪問的地方(如全局變量),那么所有線程都可以訪問該Thread對象。 如果你想原子地修改另一個線程可以訪問的任何東西,你必須用鎖來保護它。 並且所有線程當然必須共享這個非常相同的鎖,否則它不會很有效。

如果你想要實際的線程本地存儲,那就是threading.local用武之地。 threading.local屬性在threading.local之間不共享; 每個線程只能看到它自己放置在那里的屬性。 如果您對其實現感到好奇,源代碼位於標准庫中的_threading_local.py中。

考慮以下代碼:

#/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()
I'm called from Thread-2
I'm called from 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()
I'm called from Thread-2
I'm called from 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())

但是當使用第三方或設計不佳的代碼時,這並不總是可行的。

您可以使用threading.local()創建線程本地存儲。

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

存儲到 tls 的數據對於每個線程都是唯一的,這將有助於確保不會發生無意的共享。

就像在其他所有語言中一樣,Python 中的每個線程都可以訪問相同的變量。 “主線程”和子線程之間沒有區別。

與 Python 的一個區別是 Global Interpreter Lock 意味着一次只能有一個線程運行 Python 代碼。 然而,當涉及到同步訪問時,這並沒有多大幫助,因為所有常見的搶占問題仍然適用,並且您必須像在其他語言中一樣使用線程原語。 但是,這確實意味着您需要重新考慮是否使用線程來提高性能。

我可能在這里錯了。 如果您知道其他情況,請加以說明,因為這將有助於解釋為什么需要使用線程 local()。

這句話看起來不對,沒有錯:“如果你想原子地修改另一個線程可以訪問的任何東西,你必須用鎖來保護它。” 我認為這種說法是 ->有效<- 正確但不完全准確。 我認為術語“原子”意味着 Python 解釋器創建了一個字節碼塊,沒有給 CPU 留下中斷信號的空間。

我認為原子操作是無法訪問中斷的 Python 字節代碼塊。 像“running = True”這樣的 Python 語句是原子的。 在這種情況下,您不需要從中斷中鎖定 CPU(我相信)。 Python 字節碼分解對於線程中斷是安全的。

像“threads_running[5] = True”這樣的 Python 代碼不是原子的。 這里有兩塊 Python 字節碼; 一個用於取消引用對象的 list() 和另一個字節代碼塊以將值分配給對象,在這種情況下是列表中的“位置”。 可以引發中斷-->介於<- 兩個字節碼->塊<-。 那就是壞事發生了。

線程 local() 與“原子”有何關系? 這就是為什么該聲明似乎誤導了我。 如果不是你能解釋一下嗎?

值得一提的是threading.local()不是單例。

您可以在每個線程中使用更多。 不是一種存儲

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM