답변:
스레드 로컬 저장 기간은 전역 또는 정적 저장 기간 인 것처럼 보이지만 (데이터를 사용하는 함수의 관점에서) 실제로는 스레드 당 하나의 사본이 있습니다.
현재 자동 (블록 / 기능 중에 존재), 정적 (프로그램 기간 동안 존재) 및 동적 (할당과 할당 해제 사이의 힙에 존재)에 추가합니다.
스레드 로컬 인 것이 스레드 생성시 존재하며 스레드 중지시 폐기됩니다.
다음은 몇 가지 예입니다.
시드가 스레드별로 유지 관리되어야하는 난수 생성기를 생각하십시오. 스레드 로컬 시드를 사용한다는 것은 각 스레드가 다른 스레드와 상관없이 고유 한 난수 시퀀스를 얻는다는 의미입니다.
시드가 임의 함수 내에서 로컬 변수 인 경우 호출 할 때마다 초기화되어 매번 동일한 번호를 제공합니다. 그것이 전역이라면 스레드는 서로의 시퀀스를 방해합니다.
또 다른 예는 strtok
토큰 화 상태가 스레드 별 기준으로 저장되는 것과 같은 것 입니다. 이렇게하면 단일 스레드가 다른 스레드가 토큰 화 노력을 망칠 수는 없지만 여러 호출을 통해 상태를 유지할 수는 있지만 strtok
기본적으로 strtok_r
(스레드 안전 버전)이 중복됩니다.
이 두 예제는 스레드 로컬 변수가 변수를 사용하는 함수 내에 존재할 수 있도록 합니다. 사전 스레드 코드에서는 단순히 함수 내의 정적 저장 기간 변수입니다. 스레드의 경우 로컬 저장소 기간을 스레드하도록 수정되었습니다.
또 다른 예는 다음과 같습니다 errno
. errno
호출 중 하나가 실패한 후 변수를 확인할 수 있기 전에 별도의 스레드 수정 을 원하지 않지만 스레드 당 하나의 사본 만 원합니다.
r
"재진입" 의 약자이며, 이는 스레드 안전과 관련이 없습니다. 스레드 로컬 저장소를 사용하여 스레드 안전하게 작업 할 수 있지만 다시 입력 할 수는 없습니다.
strtok
다른 함수를 호출해야하는 이유는 없습니다 .
while (something) { char *next = strtok(whatever); someFunction(next); // someFunction calls strtok }
변수 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"일 수 있습니다.
g()
의 시작에 전화를 threadFunc
한 후 출력이 될 것입니다, 0304029
또는 쌍의 다른 순열 02
, 03
및 04
. 즉, i
스레드가 작성되기 전에 9가 할당 되더라도 스레드는 새로 작성된 i
where 사본을 얻습니다 i=0
. 가로 i
지정 thread_local int i = random_integer()
되면 각 스레드는 새로운 임의의 정수를 얻습니다.
02
, 03
, 04
, 등의 다른 시퀀스가있을 수 있습니다020043
스레드 로컬 저장소는 정적 (= 전역) 저장소와 같은 모든 측면에서 각 스레드마다 별도의 개체 복사본이 있습니다. 객체의 수명은 스레드 시작 (글로벌 변수의 경우) 또는 첫 번째 초기화 (블록 로컬 정적의 경우)에서 시작하고 스레드가 종료 될 때 (즉, 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;
}
};
strtok
.strtok
단일 스레드 환경에서도 손상됩니다.