program story

Python의 스레드 로컬 저장소

inputbox 2020. 11. 30. 08:07
반응형

Python의 스레드 로컬 저장소


Python에서 스레드 로컬 저장소를 어떻게 사용합니까?

관련


스레드 로컬 스토리지는 스레드 작업자 풀이 있고 각 스레드가 네트워크 또는 데이터베이스 연결과 같은 자체 리소스에 액세스해야하는 경우에 유용합니다. 점을 유의 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]

... 물론 스레드 ID와 각각의 수와 순서는 실행마다 다릅니다.


스레드 로컬 저장소는 단순히 네임 스페이스 (속성 표기법을 통해 액세스 된 값 포함)로 생각할 수 있습니다. 차이점은 각 스레드가 자체 속성 / 값 집합을 투명하게 가져 오므로 한 스레드가 다른 스레드의 값을 볼 수 없다는 것입니다.

일반 객체와 마찬가지로 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

Note how each thread maintains its own counter, even though the ns attribute is a class member (and hence shared between the threads).

The same example could have used an instance variable or a local variable, but that wouldn't show much, as there's no sharing then (a dict would work just as well). There are cases where you'd need thread-local storage as instance variables or local variables, but they tend to be relatively rare (and pretty subtle).


As noted in the question, Alex Martelli gives a solution here. This function allows us to use a factory function to generate a default value for each thread.

#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

Can also write

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

mydata.x will only exist in the current thread


My way of doing a thread local storage across modules / files. The following has been tested in 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

In fileA, I start a thread which has a target function in another module/file.

In fileB, I set a local variable I want in that thread.

In fileC, I access the thread local variable of the current thread.

Additionally, just print 'dictionary' variable so that you can see the default values available, like kwargs, args, etc.

참고URL : https://stackoverflow.com/questions/1408171/thread-local-storage-in-python

반응형