Поток локального хранилища в Python - PullRequest
58 голосов
/ 11 сентября 2009

Как использовать локальное хранилище потоков в Python?

Относящиеся

Ответы [ 5 ]

95 голосов
/ 06 ноября 2012

Локальное хранилище потока полезно, например, если у вас есть рабочий пул потоков, и каждый поток нуждается в доступе к своему собственному ресурсу, такому как соединение с сетью или базой данных. Обратите внимание, что модуль threading использует обычную концепцию потоков (которые имеют доступ к глобальным данным процесса), но они не слишком полезны из-за глобальной блокировки интерпретатора. Различный модуль multiprocessing создает новый подпроцесс для каждого, поэтому любой глобальный будет локальным потоком.

резьбовой модуль

Вот простой пример:

import threading
from threading import current_thread

threadLocal = threading.local()

def hi():
    initialized = getattr(threadLocal, 'initialized', None)
    if initialized is None:
        print("Nice to meet you", current_thread().name)
        threadLocal.initialized = True
    else:
        print("Welcome back", current_thread().name)

hi(); hi()

Это распечатает:

Nice to meet you MainThread
Welcome back MainThread

Одна важная вещь, которую легко упустить из виду: объект threading.local() нужно создавать только один раз, а не один раз для потока или для вызова функции. Уровень global или class - идеальные места.

Вот почему: threading.local() фактически создает новый экземпляр каждый раз, когда он вызывается (как любой вызов фабрики или класса), поэтому многократный вызов threading.local() постоянно перезаписывает исходный объект, что по всей вероятности что хочешь Когда какой-либо поток обращается к существующей переменной threadLocal (или как она там называется), он получает свое собственное частное представление этой переменной.

Это не будет работать как задумано:

import threading
from threading import current_thread

def wont_work():
    threadLocal = threading.local() #oops, this creates a new dict each time!
    initialized = getattr(threadLocal, 'initialized', None)
    if initialized is None:
        print("First time for", current_thread().name)
        threadLocal.initialized = True
    else:
        print("Welcome back", current_thread().name)

wont_work(); wont_work()

Результатом будет:

First time for MainThread
First time for MainThread

многопроцессорный модуль

Все глобальные переменные являются локальными для потока, поскольку модуль multiprocessing создает новый процесс для каждого потока.

Рассмотрим этот пример, где счетчик processed является примером локального хранилища потока:

from multiprocessing import Pool
from random import random
from time import sleep
import os

processed=0

def f(x):
    sleep(random())
    global processed
    processed += 1
    print("Processed by %s: %s" % (os.getpid(), processed))
    return x*x

if __name__ == '__main__':
    pool = Pool(processes=4)
    print(pool.map(f, range(10)))

Будет выведено что-то вроде этого:

Processed by 7636: 1
Processed by 9144: 1
Processed by 5252: 1
Processed by 7636: 2
Processed by 6248: 1
Processed by 5252: 2
Processed by 6248: 2
Processed by 9144: 2
Processed by 7636: 3
Processed by 5252: 3
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

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

16 голосов
/ 21 апреля 2015

Локальное хранилище потока можно просто представить как пространство имен (со значениями, доступными через нотацию атрибута). Разница в том, что каждый поток прозрачно получает свой собственный набор атрибутов / значений, так что один поток не видит значения из другого потока.

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

Вот простой пример:

import threading

class Worker(threading.Thread):
    ns = threading.local()
    def run(self):
        self.ns.val = 0
        for i in range(5):
            self.ns.val += 1
            print("Thread:", self.name, "value:", self.ns.val)

w1 = Worker()
w2 = Worker()
w1.start()
w2.start()
w1.join()
w2.join()

Выход:

Thread: Thread-1 value: 1
Thread: Thread-2 value: 1
Thread: Thread-1 value: 2
Thread: Thread-2 value: 2
Thread: Thread-1 value: 3
Thread: Thread-2 value: 3
Thread: Thread-1 value: 4
Thread: Thread-2 value: 4
Thread: Thread-1 value: 5
Thread: Thread-2 value: 5

Обратите внимание, как каждый поток поддерживает свой собственный счетчик, даже если атрибут ns является членом класса (и, следовательно, совместно используется потоками).

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

16 голосов
/ 11 сентября 2009

Как отмечено в вопросе, Алекс Мартелли дает решение здесь . Эта функция позволяет нам использовать фабричную функцию для генерации значения по умолчанию для каждого потока.

#Code originally posted by Alex Martelli
#Modified to use standard Python variable name conventions
import threading
threadlocal = threading.local()    

def threadlocal_var(varname, factory, *args, **kwargs):
  v = getattr(threadlocal, varname, None)
  if v is None:
    v = factory(*args, **kwargs)
    setattr(threadlocal, varname, v)
  return v
5 голосов
/ 11 сентября 2009

Можно также написать

import threading
mydata = threading.local()
mydata.x = 1

mydata.x будет существовать только в текущем потоке

1 голос
/ 15 апреля 2019

Мой способ сделать поток локальным хранилищем по модулям / файлам . Следующее было протестировано в Python 3.5 -

import threading
from threading import current_thread

# fileA.py 
def functionOne:
    thread = Thread(target = fileB.functionTwo)
    thread.start()

#fileB.py
def functionTwo():
    currentThread = threading.current_thread()
    dictionary = currentThread.__dict__
    dictionary["localVar1"] = "store here"   #Thread local Storage
    fileC.function3()

#fileC.py
def function3():
    currentThread = threading.current_thread()
    dictionary = currentThread.__dict__
    print (dictionary["localVar1"])           #Access thread local Storage

В файле A я запускаю поток с целевой функцией в другом модуле / файле.

В файле B я устанавливаю локальную переменную, которую я хочу в этом потоке.

В файле C я обращаюсь к локальной переменной потока текущего потока.

Кроме того, просто распечатайте переменную словаря, чтобы увидеть доступные значения по умолчанию, такие как kwargs, args и т. Д. .

...