回答:
スレッドローカルストレージ期間は、グローバルまたは静的なストレージ期間(それを使用する関数の観点から)のように見えるデータを指すために使用される用語ですが、実際には、スレッドごとに1つのコピーがあります。
現在の自動(ブロック/関数中に存在)、静的(プログラム期間中に存在)、動的(割り当てと割り当て解除の間のヒープに存在)に追加されます。
スレッドローカルなものは、スレッドの作成時に存在し、スレッドが停止すると破棄されます。
次にいくつかの例を示します。
シードをスレッドごとに維持する必要がある乱数ジェネレータを考えてみます。スレッドローカルシードを使用すると、各スレッドは、他のスレッドとは関係なく、独自の乱数シーケンスを取得します。
シードがランダム関数内のローカル変数である場合、それを呼び出すたびに初期化され、毎回同じ数が得られます。グローバルの場合、スレッドは互いのシーケンスを妨害します。
別の例はstrtok
、トークン化の状態がスレッド固有のベースで格納されるようなものです。このようにして、単一のスレッドは、他のスレッドがトークン化の作業を台無しにせず、複数の呼び出しに対して状態を維持できることを確認できます。これにより、strtok
基本的にstrtok_r
(スレッドセーフバージョン)が冗長になります。
これらの例はどちらも、スレッドローカル変数がそれを使用する関数内に存在できるようにします。事前にスレッド化されたコードでは、関数内の静的ストレージ期間変数にすぎません。スレッドの場合、スレッドのローカルストレージ期間に変更されます。
さらに別の例は、のようなものですerrno
。errno
呼び出しの1つが失敗した後で、変数をチェックする前に、個別のスレッドを変更する必要はありませんが、スレッドごとに1つのコピーのみが必要です。
このサイトには、さまざまなストレージ期間指定子の合理的な説明があります。
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();
}
このプログラムには、メインスレッドと手動で作成したスレッドの2つのスレッドがあります。どちらのスレッドもを呼び出さない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
シングルスレッド環境でも壊れます。