스레드 로컬 스토리지컴퓨터 프로그래밍에서 스레드 로컬 스토리지(Thread-local storage, TLS)는 정적 또는 전역 메모리를 스레드에 지역적으로 사용하는 메모리 관리 방법이다. 이 개념은 별도의 스레드가 있는 시스템에서 전역처럼 보이는 데이터를 저장할 수 있도록 한다. 많은 시스템은 스레드 지역 메모리 블록의 크기에 제한을 두며, 실제로 상당히 엄격한 제한을 두기도 한다. 반면에, 시스템이 적어도 메모리 주소(포인터) 크기의 변수 스레드 지역을 제공할 수 있다면, 이는 그러한 메모리 블록을 동적으로 할당하고 해당 블록의 메모리 주소를 스레드 지역 변수에 저장함으로써 임의 크기의 메모리 블록을 스레드 지역 방식으로 사용할 수 있게 한다. RISC 머신에서는 호출 규약이 종종 이 용도를 위해 스레드 포인터 레지스터를 예약한다. 용례전역 변수의 사용은 일반적으로 현대 프로그래밍에서 권장되지 않지만, 유닉스와 같은 일부 오래된 운영 체제는 원래 단일 프로세서 하드웨어를 위해 설계되었으며 중요한 값을 저장하는 데 전역 변수를 자주 사용한다. 한 예로 C 라이브러리의 많은 함수에서 사용되는 두 번째 사용 사례는 여러 스레드가 정보를 전역 변수에 누적하는 경우이다. 경쟁 상태를 피하기 위해 이 전역 변수에 대한 모든 접근은 뮤텍스로 보호되어야 한다. 대신, 각 스레드는 스레드 지역 변수에 누적하여 경쟁 상태의 가능성을 제거하고 락의 필요성을 없앨 수 있다. 그러면 스레드는 자체 스레드 지역 변수에서 단일 전역 변수로 최종 누적을 동기화하기만 하면 된다. 윈도우 구현응용 프로그래밍 인터페이스(API) 함수
각 스레드에는 Win32 스레드 정보 블록이 있다. 이 블록의 항목 중 하나는 해당 스레드의 스레드 로컬 스토리지 테이블이다.[1]
TlsXxx 함수군 외에도 Windows 실행 파일은 실행 중인 프로세스의 각 스레드에 대해 다른 페이지에 매핑되는 섹션을 정의할 수 있다. TlsXxx 값과 달리 이러한 페이지는 임의의 유효한 주소를 포함할 수 있다. 그러나 이러한 주소는 각 실행 스레드마다 다르므로 비동기 함수(다른 스레드에서 실행될 수 있음)에 전달하거나 가상 주소가 전체 프로세스 내에서 고유하다고 가정하는 코드에 전달해서는 안 된다. TLS 섹션은 메모리 페이징을 사용하여 관리되며 그 크기는 페이지 크기(x86 머신에서는 4KB)로 양자화된다. 이러한 섹션은 프로그램의 주 실행 파일 내에서만 정의될 수 있다. DLL에는 이러한 섹션을 포함해서는 안 된다. LoadLibrary로 로드할 때 올바르게 초기화되지 않기 때문이다. POSIX 스레드 구현POSIX 스레드 API에서 스레드에 지역적인 메모리는 스레드 특정 데이터(Thread-specific data)라는 용어로 지정된다.
또한 언어별 구현프로그래머가 적절한 API 함수를 호출하는 것에 의존하는 것 외에도 스레드 로컬 스토리지(TLS)를 지원하도록 프로그래밍 언어를 확장하는 것도 가능하다. C 및 C++C11에서는 #include <threads.h>
thread_local int foo = 0;
C11에서 C++11은 다음과 같은 경우에 사용할 수 있는
그 외에도 다양한 컴파일러 구현은 스레드 지역 변수를 선언하는 특정 방법을 제공한다.
비스타 및 서버 2008 이전의 윈도우 버전에서는 커먼 리스프 및 기타 방언동적 변수는 함수 호출과 해당 함수가 호출하는 모든 자식 함수에 대해 비공개인 바인딩을 가진다. 이 추상화는 자연스럽게 스레드 특정 저장소에 매핑되며, 스레드를 제공하는 리스프 구현은 이를 수행한다. 커먼 리스프에는 수많은 표준 동적 변수가 있으며, 따라서 동적 바인딩에서 이러한 변수가 스레드 지역 의미론을 갖지 않으면 언어 구현에 스레드를 합리적으로 추가할 수 없다. 예를 들어, 표준 변수 ;;; 함수 foo와 그 자식 함수는
;; 16진수로 인쇄한다.
(let ((*print-base* 16)) (foo))
함수가 다른 스레드에서 동시에 실행될 수 있다면 이 바인딩은 스레드 지역이어야 한다. 그렇지 않으면 각 스레드가 전역 인쇄 진수를 제어하기 위해 서로 싸울 것이다. DD 버전 2에서는 모든 정적 및 전역 변수가 기본적으로 스레드 지역이며 다른 언어의 "일반" 전역 및 정적 변수와 유사한 구문으로 선언된다. 전역 변수는 shared 키워드를 사용하여 명시적으로 요청해야 한다. int threadLocal; // 이것은 스레드 지역 변수이다.
shared int global; // 이것은 모든 스레드와 공유되는 전역 변수이다.
shared 키워드는 저장소 클래스이자 타입 한정자 역할을 한다. 공유 변수는 데이터 무결성을 정적으로 강제하는 몇 가지 제한 사항이 있다.[13] 이러한 제한 없이 "고전적인" 전역 변수를 선언하려면 안전하지 않은 __gshared 키워드를 사용해야 한다.[14] __gshared int global; // 이것은 일반적인 전역 변수이다.
자바자바에서는 스레드 지역 변수가 private static final ThreadLocal<Integer> myThreadLocalInteger = new ThreadLocal<Integer>();
적어도 Oracle/OpenJDK의 경우, 자바 스레딩의 다른 측면에서 OS 스레드가 사용되더라도 이는 네이티브 스레드 로컬 스토리지를 사용하지 않는다. 대신, 각 Thread 객체는 ThreadLocal 객체에서 해당 값으로 매핑되는(Thread 객체에서 값으로 매핑되는 ThreadLocal을 가져와 성능 오버헤드를 발생시키는 것과 반대) (스레드 안전하지 않은) 맵을 저장한다.[16] .NET 언어: C# 및 기타닷넷 프레임워크 언어(예: C 샤프)에서는 정적 필드를 ThreadStatic 속성으로 표시할 수 있다.[17]:898 class FooBar
{
[ThreadStatic]
private static int _foo;
}
.NET Framework 4.0에서는 System.Threading.ThreadLocal<T> 클래스를 사용하여 스레드 지역 변수를 할당하고 지연 로딩할 수 있다.[17]:899 class FooBar
{
private static System.Threading.ThreadLocal<int> _foo;
}
또한 스레드 지역 변수를 동적으로 할당하는 API도 사용할 수 있다.[17]:899-890 오브젝트 파스칼오브젝트 파스칼 (델파이) 또는 프리 파스칼에서는 'var' 대신 'threadvar' 예약어를 사용하여 스레드 로컬 스토리지를 사용하는 변수를 선언할 수 있다. var
mydata_process: integer;
threadvar
mydata_threadlocal: integer;
오브젝티브-C코코아, 그누스텝, 오픈스텝에서 각 NSMutableDictionary *dict = [[NSThread currentThread] threadDictionary];
dict[@"A key"] = @"Some data";
펄펄에서는 언어 진화 후반에 스레드가 추가되었는데, 이미 CPAN에 많은 기존 코드가 존재한 이후였다. 따라서 펄에서는 기본적으로 모든 변수에 대해 자체 지역 저장소를 가져와 스레드가 기존의 스레드 비인식 코드에 미치는 영향을 최소화한다. 펄에서 스레드 공유 변수는 속성을 사용하여 생성할 수 있다. use threads;
use threads::shared;
my $localvar;
my $sharedvar :shared;
퓨어베이직퓨어베이직에서 스레드 변수는 Threaded 키워드로 선언된다. Threaded Var 파이썬파이썬 버전 2.4 이상에서는 threading 모듈의 local 클래스를 사용하여 스레드 로컬 스토리지를 생성할 수 있다. import threading
mydata = threading.local()
mydata.x = 1
local 클래스의 여러 인스턴스를 생성하여 서로 다른 변수 집합을 저장할 수 있다.[18] 따라서 이는 싱글턴이 아니다. 루비루비는 Thread.current[:user_id] = 1
러스트러스트에서는 러스트 표준 라이브러리에서 제공하는 use std::cell::RefCell;
use std::thread;
thread_local!(static FOO: RefCell<u32> = RefCell::new(1));
FOO.with(|f| {
assert_eq!(*f.borrow(), 1);
*f.borrow_mut() = 2;
});
// 이 스레드가 이미 스레드 지역 값의 복사본을 2로 변경했음에도 불구하고, 각 스레드는 초기 값인 1로 시작한다.
let t = thread::spawn(move || {
FOO.with(|f| {
assert_eq!(*f.borrow(), 1);
*f.borrow_mut() = 3;
});
});
// 스레드가 완료될 때까지 기다리고 패닉 시 종료한다.
t.join().unwrap();
// 자식 스레드가 해당 스레드의 값을 3으로 변경했음에도 불구하고, 원래 스레드는 원래 값인 2를 유지한다.
FOO.with(|f| {
assert_eq!(*f.borrow(), 2);
});
같이 보기각주
참고 문헌
외부 링크
|