C ++ 11에서 thread_local은 무엇을 의미합니까?


131

thread_localC ++ 11 의 설명과 혼동됩니다 . 내 이해는 각 스레드에는 함수에 로컬 변수의 고유 사본이 있습니다. 전역 / 정적 변수는 모든 스레드에서 액세스 할 수 있습니다 (잠금을 사용하여 동기화 된 액세스 가능). 그리고 thread_local변수는 모든 스레드에 표시되지만 정의 된 스레드 만 수정할 수 있습니까? 맞습니까?

답변:


151

스레드 로컬 저장 기간은 전역 또는 정적 저장 기간 인 것처럼 보이지만 (데이터를 사용하는 함수의 관점에서) 실제로는 스레드 당 하나의 사본이 있습니다.

현재 자동 (블록 / 기능 중에 존재), 정적 (프로그램 기간 동안 존재) 및 동적 (할당과 할당 해제 사이의 힙에 존재)에 추가합니다.

스레드 로컬 인 것이 스레드 생성시 존재하며 스레드 중지시 폐기됩니다.

다음은 몇 가지 예입니다.

시드가 스레드별로 유지 관리되어야하는 난수 생성기를 생각하십시오. 스레드 로컬 시드를 사용한다는 것은 각 스레드가 다른 스레드와 상관없이 고유 한 난수 시퀀스를 얻는다는 의미입니다.

시드가 임의 함수 내에서 로컬 변수 인 경우 호출 할 때마다 초기화되어 매번 동일한 번호를 제공합니다. 그것이 전역이라면 스레드는 서로의 시퀀스를 방해합니다.

또 다른 예는 strtok토큰 화 상태가 스레드 별 기준으로 저장되는 것과 같은 것 입니다. 이렇게하면 단일 스레드가 다른 스레드가 토큰 화 노력을 망칠 수는 없지만 여러 호출을 통해 상태를 유지할 수는 있지만 strtok기본적으로 strtok_r(스레드 안전 버전)이 중복됩니다.

이 두 예제는 스레드 로컬 변수가 변수를 사용하는 함수 내에 존재할 수 있도록 합니다. 사전 스레드 코드에서는 단순히 함수 내의 정적 저장 기간 변수입니다. 스레드의 경우 로컬 저장소 기간을 스레드하도록 수정되었습니다.

또 다른 예는 다음과 같습니다 errno. errno호출 중 하나가 실패한 후 변수를 확인할 수 있기 전에 별도의 스레드 수정 을 원하지 않지만 스레드 당 하나의 사본 만 원합니다.

이 사이트 에는 다양한 저장 기간 지정자에 대한 적절한 설명 이 있습니다 .


4
스레드 로컬을 사용해도 문제가 해결되지 않습니다 strtok. strtok단일 스레드 환경에서도 손상됩니다.
James Kanze

11
죄송합니다. 그것은 strtok에 새로운 문제를 소개하지 않습니다 :-)
paxdiablo

7
실제로, r"재진입" 의 약자이며, 이는 스레드 안전과 관련이 없습니다. 스레드 로컬 저장소를 사용하여 스레드 안전하게 작업 할 수 있지만 다시 입력 할 수는 없습니다.
Kerrek SB

5
단일 스레드 환경에서 함수는 호출 그래프에서 사이클의 일부인 경우에만 다시 입력해야합니다. 리프 함수 (다른 함수를 호출하지 않는 리프 함수)는 정의상 사이클의 일부가 아니며 strtok다른 함수를 호출해야하는 이유는 없습니다 .
MSalters

3
while (something) { char *next = strtok(whatever); someFunction(next); // someFunction calls strtok }
japreiss

135

변수 thread_local를 선언하면 각 스레드마다 고유 한 사본이 있습니다. 이름으로 참조하면 현재 스레드와 연관된 사본이 사용됩니다. 예 :

thread_local int i=0;

void f(int newval){
    i=newval;
}

void g(){
    std::cout<<i;
}

void threadfunc(int id){
    f(id);
    ++i;
    g();
}

int main(){
    i=9;
    std::thread t1(threadfunc,1);
    std::thread t2(threadfunc,2);
    std::thread t3(threadfunc,3);

    t1.join();
    t2.join();
    t3.join();
    std::cout<<i<<std::endl;
}

이 코드는 "2349", "3249", "4239", "4329", "2439"또는 "3429"를 출력하지만 다른 것은 출력하지 않습니다. 각 스레드에는 고유 한 사본이 있습니다.이 사본 i은 할당, 증분 및 인쇄됩니다. 실행중인 스레드 main에는 자체 사본이 있으며 처음에 할당 된 다음 변경되지 않습니다. 이 사본은 완전히 독립적이며 각각 다른 주소를 갖습니다.

그 점에서 특별한 이름 일뿐입니다. --- thread_local변수 의 주소를 가져 가면 일반 객체에 대한 일반적인 포인터가 있으며 스레드 사이를 자유롭게 전달할 수 있습니다. 예 :

thread_local int i=0;

void thread_func(int*p){
    *p=42;
}

int main(){
    i=9;
    std::thread t(thread_func,&i);
    t.join();
    std::cout<<i<<std::endl;
}

의 주소가 i스레드 함수로 전달 i되므로 기본 스레드 에 속하는 사본을 로 할당 할 수 있습니다 thread_local. 따라서이 프로그램은 "42"를 출력합니다. 이 작업을 수행하는 경우 *p스레드가 속한 스레드가 종료 된 후 액세스하지 않는 것을주의해야합니다 . 그렇지 않으면 뾰족한 개체가 파괴되는 다른 경우와 마찬가지로 매달려 포인터와 정의되지 않은 동작이 나타납니다.

thread_local변수는 "처음 사용하기 전에"초기화되므로, 주어진 스레드가 절대로 손대지 않으면 반드시 초기화 될 필요는 없습니다. 이것은 컴파일러가 thread_local프로그램에 포함 된 모든 변수를 완전히 자체적으로 포함하고 어떤 것도 건드리지 않는 스레드에 대해 구성하지 않도록 하기위한 것입니다. 예 :

struct my_class{
    my_class(){
        std::cout<<"hello";
    }
    ~my_class(){
        std::cout<<"goodbye";
    }
};

void f(){
    thread_local my_class unused;
}

void do_nothing(){}

int main(){
    std::thread t1(do_nothing);
    t1.join();
}

이 프로그램에는 메인 스레드와 수동으로 생성 된 스레드의 두 스레드가 있습니다. 스레드는 모두 호출하지 f않으므로 thread_local객체는 사용되지 않습니다. 따라서 컴파일러에서 0, 1 또는 2 개의 인스턴스를 생성할지 여부는 지정되지 my_class않으며 출력은 "", "hellohellogoodbyegoodbye"또는 "hellogoodbye"일 수 있습니다.


1
변수의 스레드 로컬 복사본이 새로 초기화 된 변수 복사본이라는 점에 유의해야한다고 생각합니다. 당신이 추가 할 경우 즉, g()의 시작에 전화를 threadFunc한 후 출력이 될 것입니다, 0304029또는 쌍의 다른 순열 02, 0304. 즉, i스레드가 작성되기 전에 9가 할당 되더라도 스레드는 새로 작성된 iwhere 사본을 얻습니다 i=0. 가로 i지정 thread_local int i = random_integer()되면 각 스레드는 새로운 임의의 정수를 얻습니다.
Mark H

아니 정확히의 순열은 02, 03, 04, 등의 다른 시퀀스가있을 수 있습니다020043
Hongxu 첸

방금 찾은 재미있는 tidbit : GCC는 thread_local 변수의 주소를 템플릿 인수로 사용하도록 지원하지만 다른 컴파일러는 (이 글을 쓰는 시점에서 clang, vstudio를 시도하지 않았습니다). 표준에 대해 무엇을 말해야하는지, 이것이 지정되지 않은 영역인지 확실하지 않습니다.
jwd

23

스레드 로컬 저장소는 정적 (= 전역) 저장소와 같은 모든 측면에서 각 스레드마다 별도의 개체 복사본이 있습니다. 객체의 수명은 스레드 시작 (글로벌 변수의 경우) 또는 첫 번째 초기화 (블록 로컬 정적의 경우)에서 시작하고 스레드가 종료 될 때 (즉, join()호출 될 때 ) 종료됩니다 .

결과적으로 선언 static될 수있는 변수 만 thread_local전역 변수 (보다 정확하게는 네임 스페이스 범위의 변수), 정적 클래스 멤버 및 블록 정적 변수 (이 경우 static암시) 로 선언 될 수 있습니다 .

예를 들어, 스레드 풀이 있고 작업로드가 얼마나 잘 균형을 이루고 있는지 알고 싶다고 가정하십시오.

thread_local Counter c;

void do_work()
{
    c.increment();
    // ...
}

int main()
{
    std::thread t(do_work);   // your thread-pool would go here
    t.join();
}

예를 들어 다음과 같은 구현으로 스레드 사용 통계를 인쇄합니다.

struct Counter
{
     unsigned int c = 0;
     void increment() { ++c; }
     ~Counter()
     {
         std::cout << "Thread #" << std::this_thread::id() << " was called "
                   << c << " times" << std::endl;
     }
};
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.