Что такое "потоковое локальное хранилище" в Python и зачем оно мне нужно?
В частности, в Python, как переменные распределяются между потоками?
Хотя я использовал threading.Thread
прежде чем я никогда не понимал и не видел примеров того, как переменные стали общими. Распределяются ли они между основным потоком и детьми или только среди детей? Когда мне нужно будет использовать локальное хранилище потоков, чтобы избежать этого общего доступа?
Я видел много предупреждений о синхронизации доступа к общим данным между потоками с помощью блокировок, но мне еще не удалось увидеть действительно хороший пример проблемы.
Заранее спасибо!
6 ответов
В Python все совместно используется, кроме локальных переменных функций (потому что каждый вызов функции получает свой собственный набор локальных объектов, а потоки всегда являются отдельными вызовами функций.) И даже тогда, только сами переменные (имена, которые относятся к объектам) являются локальными для функции; Сами объекты всегда глобальны, и что угодно может ссылаться на них. Thread
Объект для определенного потока не является специальным объектом в этом отношении. Если вы храните Thread
объект где-то все потоки могут получить доступ (как глобальная переменная), тогда все потоки могут получить доступ к этому Thread
объект. Если вы хотите атомарно изменить то, что вы не просто создали в этом же потоке, и не сохранили нигде, где другой поток может получить к нему доступ, вы должны защитить его блокировкой. И все потоки, конечно же, должны использовать одну и ту же блокировку, иначе она не будет очень эффективной.
Если вы хотите фактическое локальное хранилище потока, вот где 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(); Т (). Начать () Меня зовут из Нити-2 Меня зовут из Нити-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(); Т (). Начать () Меня зовут из Нити-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())
Но это не всегда возможно при использовании стороннего или плохо разработанного кода.
Вы можете создать локальное хранилище потока, используя threading.local()
,
>>> tls = threading.local()
>>> tls.x = 4
>>> tls.x
4
Данные, хранящиеся в tls, будут уникальными для каждого потока, что поможет избежать непреднамеренного обмена.
Как и в любом другом языке, каждый поток в Python имеет доступ к одним и тем же переменным. Там нет различий между "основным потоком" и дочерними потоками.
Одно отличие от Python состоит в том, что глобальная блокировка интерпретатора означает, что только один поток может одновременно выполнять код Python. Это не очень помогает, когда дело доходит до синхронизации доступа, так как все обычные проблемы с преимуществами все еще применяются, и вы должны использовать потоковые примитивы, как и в других языках. Это означает, что вам нужно пересмотреть, если вы использовали потоки для производительности, однако.
Стоит отметить
threading.local()
не является синглтоном.
Вы можете использовать больше из них в потоке. Это не одно хранилище.
Я могу ошибаться здесь. Если вы знаете иное, поясните, что это поможет объяснить, почему нужно использовать поток local().
Это утверждение кажется неправильным, а не неправильным: "Если вы хотите атомарно изменить что-либо, к чему другой поток имеет доступ, вы должны защитить это блокировкой". Я думаю, что это утверждение -> эффективно<- верно, но не совсем точно. Я думал, что термин "атомарный" означает, что интерпретатор Python создает фрагмент байт-кода, который не оставляет места для сигнала прерывания для процессора.
Я думал, что атомарные операции - это куски байтового кода Python, которые не дают доступа к прерываниям. Такие операторы Python, как "running = True", атомарны. В этом случае вам не нужно блокировать процессор от прерываний (я считаю). Разбивка байтового кода Python защищена от прерывания потока.
Код Python, такой как "thread_running[5] = True", не является атомарным. Здесь есть два фрагмента байтового кода Python; один для отмены ссылки на list() для объекта и другой фрагмент байтового кода для присвоения значения объекту, в данном случае "место" в списке. Может быть вызвано прерывание -> между<- двумя байтовыми кодами ->фрагментами<-. Это были плохие вещи.
Как thread local() соотносится с "атомарным"? Вот почему это утверждение мне кажется неверным. Если нет, вы можете объяснить?