web-dev-qa-db-ja.com

Pythonの「スレッドローカルストレージ」とは何ですか。なぜ必要なのですか?

Python具体的には、スレッド間で変数をどのように共有しますか?

threading.Threadを使用したことがありますが、変数の共有方法の例を実際に理解したり、見たりすることはありませんでした。それらはメインスレッドと子の間で共有されますか、それとも子の間でのみ共有されますか?この共有を回避するためにスレッドローカルストレージを使用する必要があるのはいつですか?

ロックを使用してスレッド間で共有データへのアクセスを同期することについて多くの警告を見てきましたが、問題の本当に良い例をまだ見ていません。

前もって感謝します!

87
Mike

Pythonでは、関数ローカル変数を除き、すべてが共有されます(各関数呼び出しは独自のローカルセットを取得し、スレッドは常に別個の関数呼び出しであるため)。それでも、変数自体(オブジェクトを参照する名前)のみ関数に対してローカルです。オブジェクト自体は常にグローバルであり、何でもそれらを参照できます。特定のスレッドのThreadオブジェクトは、この点で特別なオブジェクトではありません。 Threadオブジェクトをすべてのスレッドがアクセスできる場所(グローバル変数など)に保存すると、すべてのスレッドがその1つのThreadオブジェクトにアクセスできます。このまったく同じスレッドで作成したのではなく、他のスレッドが取得できる場所を保存しなかったanythingをアトミックに変更する場合、ロックで保護する必要があります。そしてもちろん、すべてのスレッドがこのまったく同じロックを共有しなければなりません。

実際のスレッドローカルストレージが必要な場合は、threading.localを使用します。threading.localの属性はスレッド間で共有されません。各スレッドは、それ自体に配置された属性のみを参照します。その実装に興味がある場合、ソースは標準ライブラリの _ threading_local.py にあります。

74
Thomas Wouters

次のコードを検討してください。

#/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()
スレッド-2から呼び出されます
スレッド-1から呼び出されます 

Threading.local()は、foo()のインターフェースを変更せずに、run()からbar()にデータを渡すための迅速で汚い方法として使用されます。

グローバル変数を使用してもうまくいかないことに注意してください。

#/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()
スレッド2から呼び出されます
スレッド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())

しかし、サードパーティのコードまたは設計が不十分なコードを使用する場合、これは常に可能とは限りません。

65
ahatchkins

threading.local()を使用して、スレッドローカルストレージを作成できます。

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

Tlsに保存されるデータは各スレッドに固有であり、意図しない共有が発生しないようにします。

17
Aaron Maenpaa

他のすべての言語と同様に、Pythonのすべてのスレッドは同じ変数にアクセスできます。 「メインスレッド」と子スレッドの間に違いはありません。

Pythonとの1つの違いは、グローバルインタープリターロックは、一度に1つのスレッドのみがPythonコードを実行できることを意味することです。ただし、通常のプリエンプションの問題はすべて適用され、他の言語と同様にスレッドプリミティブを使用する必要があるため、アクセスの同期に関してはあまり役に立ちません。ただし、パフォーマンスのためにスレッドを使用している場合は、再検討する必要があります。

2
Nick Johnson