C ++ 11에서 thread_local은 무엇을 의미합니까? 11 의 설명과 혼동됩니다

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



답변

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

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

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

다음은 몇 가지 예입니다.

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

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

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

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

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

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


답변

변수 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”일 수 있습니다.


답변

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