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


100

特にPythonでは、変数はどのようにスレッド間で共有されますか?

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

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

前もって感謝します!


2
タイトルが質問と一致しません。問題は、スレッド間で変数を共有することです。タイトルは、スレッドローカルストレージに特化していることを示しています
Casebash

2
@Casebash:この質問の音から、マイクは共有データによって引き起こされる問題を回避するためにTLSが必要であると読みましたが、デフォルトで共有されるデータ、共有されるデータ、および共有される方法については不明でした。質問と一致するようにタイトルを調整しました。
Shog9 2009

回答:


83

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

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


1
次の文について詳しく教えてください。「このまったく同じスレッドで作成しただけではなく、別のスレッドがアクセスできる場所に保存しなかったものをアトミックに変更したい場合は、ロックで保護する必要があります。」
changyuheng

@changyuheng:これは、アトミックアクションとは何かについての説明です。cs.nott.ac.uk
Tom Busby

1
@TomBusby:他のスレッドがそれに到達できない場合、なぜロックによってそれを保護する必要があるのですか、つまり、なぜプロセスをアトミックにする必要があるのですか?
changyuheng

2
「オブジェクト自体は常にグローバルであり、何でも参照できる」という簡単な例を挙げてください。参考までに、割り当て/追加ではなく、読み取りを意味すると想定しますか?
変数


75

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

#/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())

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


18

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

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

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


2

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

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


0

私はここで間違っているかもしれません。これがスレッドlocal()を使用する必要がある理由を説明するのに役立つので、他に知っている場合は説明してください。

このステートメントは間違っているようです。「別のスレッドがアクセスできるものをアトミックに変更したい場合は、ロックで保護する必要があります。」このステートメントは->事実上<-正しいと思いますが、完全に正確ではありません。「アトミック」という用語は、Pythonインタープリターがバイトコードチャンクを作成し、CPUへの割り込み信号の余地がないことを意味すると考えました。

アトミック操作は、割り込みへのアクセスを許可しないPythonバイトコードのチャンクであると思いました。「running = True」のようなPythonステートメントはアトミックです。この場合、CPUを割り込みからロックする必要はありません(私は信じています)。Pythonバイトコードの内訳は、スレッドの中断から安全です。

「threads_running [5] = True」のようなPythonコードはアトミックではありません。ここには、Pythonバイトコードのチャンクが2つあります。1つはオブジェクトのlist()を逆参照し、もう1つはオブジェクトに値を割り当てるためのバイトコードチャンク(この場合はリスト内の「場所」)です。割り込みを発生させることができます->間<-2つのバイトコード->チャンク<-。それは悪いことが起こったということです。

スレッドlocal()は「アトミック」とどのように関連していますか?これが、私にとって誤解を招くように見える理由です。できない場合は説明できますか?


1
これは答えのように見えますが、問題があると報告されていました。回答の明確化を求めることは避けます。これがコメントの目的です。
ダーマン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.