В частности, в Python, как переменные распределяются между потоками?
Хотя я раньше использовал threading.Thread, я никогда не понимал и не видел примеров того, как разделяются переменные. Распространены ли они между основным потоком и детьми или только между детьми? Когда мне нужно будет использовать локальное хранилище потоков, чтобы избежать этого совместного использования?
Я видел много предупреждений о синхронизации доступа к общим данным между потоками с помощью блокировок, но мне еще предстоит увидеть действительно хороший пример проблемы.
Заранее спасибо!
@Casebash: судя по звучанию этого вопроса, Майк прочитал, что TLS необходим, чтобы избежать проблем, вызванных общими данными, но было неясно, какие данные были переданы по умолчанию, с чем они были переданы и как они стали общими. Я изменил заголовок, чтобы он лучше соответствовал вопросу.






В Python все является общим, за исключением локальных переменных функции (поскольку каждый вызов функции получает свой собственный набор локальных переменных, а потоки всегда являются отдельными вызовами функций). И даже тогда только сами переменные (имена, которые относятся к объектам) являются локальными по отношению к функции; сами объекты всегда глобальны, и на них может ссылаться что угодно.
В этом отношении объект Thread для конкретного потока не является особым объектом. Если вы храните объект Thread где-нибудь, к которому могут получить доступ все потоки (например, к глобальной переменной), тогда все потоки могут получить доступ к этому одному объекту Thread. Если вы хотите атомарно изменить что-либо, к которому другой поток имеет доступ, вы должны защитить его блокировкой. И все потоки, конечно же, должны использовать одну и ту же блокировку, иначе это будет не очень эффективно.
Если вам нужно фактическое локальное хранилище потока, то здесь пригодится threading.local. Атрибуты threading.local не используются совместно между потоками; каждый поток видит только те атрибуты, в которые он сам поместил. Если вам интересно узнать о его реализации, исходный код находится в _threading_local.py в стандартной библиотеке.
Не могли бы вы подробнее рассказать о следующем предложении? «Если вы хотите атомарно изменить что-либо, что вы не только что создали в этом же самом потоке, и не храните нигде, куда другой поток может получить доступ, вы должны защитить это блокировкой».
@changyuheng: Вот объяснение того, что такое атомарные действия: cs.nott.ac.uk/~psznza/G52CON/lecture4.pdf
@TomBusby: если другие потоки не могут добраться до него, зачем нам защищать его блокировкой, т.е. зачем нам делать процесс атомарным?
Приведите, пожалуйста, краткий пример: «сами объекты всегда глобальны, и на них может ссылаться что угодно». Под ссылкой вы подразумеваете чтение, а не назначение / добавление?
@variable: я думаю, он имеет в виду значения не имеют объема
Как и в любом другом языке, каждый поток в Python имеет доступ к одним и тем же переменным. Нет никакого различия между «основным потоком» и дочерними потоками.
Одно отличие от Python заключается в том, что глобальная блокировка интерпретатора означает, что только один поток может запускать код Python одновременно. Однако это не очень помогает, когда дело доходит до синхронизации доступа, поскольку все обычные проблемы с приоритетом по-прежнему актуальны, и вы должны использовать примитивы потоковой передачи, как и в других языках. Однако это означает, что вам нужно пересмотреть, использовали ли вы потоки для повышения производительности.
Вы можете создать локальное хранилище потоков с помощью threading.local().
>>> tls = threading.local()
>>> tls.x = 4
>>> tls.x
4
Данные, хранящиеся в tls, будут уникальными для каждого потока, что поможет предотвратить непреднамеренное совместное использование.
Рассмотрим следующий код:
#/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())
Но это не всегда возможно при использовании стороннего или плохо спроектированного кода.
Я могу ошибаться здесь. Если вы знаете иное, поясните, что это поможет объяснить, почему нужно использовать поток local ().
Это утверждение кажется неправильным, а не неправильным: «Если вы хотите атомарно изменить что-либо, к чему другой поток имеет доступ, вы должны защитить это блокировкой». Я думаю, что это утверждение -> эффективно <- верно, но не совсем точно. Я думал, что термин «атомарный» означает, что интерпретатор Python создает фрагмент байт-кода, который не оставляет места для сигнала прерывания для ЦП.
Я думал, что атомарные операции - это куски байт-кода Python, которые не дают доступа к прерываниям. Такие операторы Python, как «running = True», атомарны. В этом случае вам не нужно блокировать процессор от прерываний (я считаю). Разбивка байтового кода Python защищена от прерывания потока.
Код Python, такой как «thread_running [5] = True», не является атомарным. Здесь есть два фрагмента байтового кода Python; один для отмены ссылки на list () для объекта и другой фрагмент байтового кода для присвоения значения объекту, в данном случае «место» в списке. Прерывание может быть вызвано -> между <- двумя байтовыми кодами -> фрагментами <-. Это были плохие вещи.
Как thread local () соотносится с "атомарным"? Вот почему это утверждение мне кажется неверным. Если нет, вы можете объяснить?
Это похоже на ответ, но я полагаю, что это было проблематично из-за заданных вопросов. Я бы не стал просить разъяснений в ответе. Вот для чего нужны комментарии.
Заголовок не соответствует вопросу. Вопрос в том, чтобы делиться переменными между потоками, название подразумевает, что речь идет именно о локальном хранилище потока.