Что такое "потоковое локальное хранилище" в Python и зачем оно мне нужно?

В Python конкретно, как переменные распределяются между потоками?

Хотя я использовал threading.Thread, прежде чем я никогда не понимал и не видел примеров того, как переменные получили общий доступ. Разделяются ли они между основной нитью и детьми или только среди детей? Когда мне понадобится использовать локальное хранилище потоков, чтобы избежать этого совместного использования?

Я видел много предупреждений о синхронизации доступа к общим данным между потоками с помощью блокировок, но мне еще предстоит увидеть действительно хороший пример проблемы.

Спасибо заранее!

Ответ 1

В Python все используется совместно, за исключением функционально-локальных переменных (потому что каждый вызов функции получает свой собственный набор локалей, а потоки всегда являются отдельными вызовами функций.) И даже тогда сами переменные (имена, которые ссылаются к объектам) являются локальными для функции; сами объекты всегда являются глобальными, и все может ссылаться на них. Объект Thread для конкретного потока не является особым объектом в этом отношении. Если вы храните объект Thread где-либо, к которому могут обращаться все потоки (например, глобальная переменная), то все потоки могут получить доступ к этому объекту Thread. Если вы хотите атомизировать все, что вы создали не только в этом же потоке, и не хранили где-нибудь другой поток, вы можете его защитить, заблокировав его. И все потоки должны, разумеется, разделять эту же блокировку, или это будет не очень эффективно.

Если вы хотите фактическое локальное хранилище, то там, где threading.local. Атрибуты threading.local не разделяются между потоками; каждый поток видит только те атрибуты, которые он сам там размещает. Если вам интересно о его реализации, источник находится в _threading_local.py в стандартной библиотеке.

Ответ 2

Рассмотрим следующий код:

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

Но это не всегда возможно при использовании стороннего или плохо разработанного кода.

Ответ 3

Вы можете создать локальное хранилище потоков, используя threading.local().

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

Данные, хранящиеся в tls, будут уникальны для каждого потока, что поможет обеспечить непреднамеренное совместное использование.

Ответ 4

Как и на любом другом языке, каждый поток в Python имеет доступ к тем же переменным. Нет никакого различия между "основным потоком" и дочерними потоками.

Одно отличие от Python заключается в том, что глобальная блокировка Interpreter означает, что только один поток может одновременно запускать код Python. Однако это не очень помогает, когда дело доходит до синхронизации доступа, поскольку все обычные проблемы с упреждением все еще применяются, и вы должны использовать примитивы потоков, как на других языках. Это означает, что вам нужно пересмотреть, если вы использовали потоки для производительности.