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

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

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

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

Заранее спасибо!

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

Casebash 11.09.2009 02:57

@Casebash: судя по звучанию этого вопроса, Майк прочитал, что TLS необходим, чтобы избежать проблем, вызванных общими данными, но было неясно, какие данные были переданы по умолчанию, с чем они были переданы и как они стали общими. Я изменил заголовок, чтобы он лучше соответствовал вопросу.

Shog9 11.09.2009 05:14
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
109
2
57 391
5
Перейти к ответу Данный вопрос помечен как решенный

Ответы 5

Ответ принят как подходящий

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

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

Не могли бы вы подробнее рассказать о следующем предложении? «Если вы хотите атомарно изменить что-либо, что вы не только что создали в этом же самом потоке, и не храните нигде, куда другой поток может получить доступ, вы должны защитить это блокировкой».

changyuheng 11.05.2018 23:35

@changyuheng: Вот объяснение того, что такое атомарные действия: cs.nott.ac.uk/~psznza/G52CON/lecture4.pdf

Tom Busby 19.09.2018 23:36

@TomBusby: если другие потоки не могут добраться до него, зачем нам защищать его блокировкой, т.е. зачем нам делать процесс атомарным?

changyuheng 20.09.2018 06:46

Приведите, пожалуйста, краткий пример: «сами объекты всегда глобальны, и на них может ссылаться что угодно». Под ссылкой вы подразумеваете чтение, а не назначение / добавление?

variable 01.11.2019 15:18

@variable: я думаю, он имеет в виду значения не имеют объема

user1071847 01.07.2020 18:47

Как и в любом другом языке, каждый поток в 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 () соотносится с "атомарным"? Вот почему это утверждение мне кажется неверным. Если нет, вы можете объяснить?

Это похоже на ответ, но я полагаю, что это было проблематично из-за заданных вопросов. Я бы не стал просить разъяснений в ответе. Вот для чего нужны комментарии.

Dharman 15.02.2020 21:31

Другие вопросы по теме