2012-08-16 15 views
70

C++ 11のthread_localの説明と混同しています。私の理解は、各スレッドは、関数内のローカル変数の一意のコピーを持っています。グローバル/静的変数は、すべてのスレッド(ロックを使用して同期化されたアクセス)によってアクセスできます。そして、thread_local変数はすべてのスレッドで見ることができますが、定義されているスレッドだけで変更できますか?それが正しいか?C++ 11ではthread_localの意味は何ですか?

答えて

73

スレッドローカルストレージ期間とは、グローバルまたは静的な記憶期間(それを使用する機能の観点から)のデータを参照するために使用される用語ですが、実際にはスレッドごとに1つのコピーが存在します。

現在の自動(ブロック/関数の間に存在する)、静的(プログラムの継続時間のために存在する)、動的(割り当てと解放の間のヒープ上に存在する)に追加されます。

スレッドローカルであるものは、スレッドの作成時に存在し、スレッドが停止したときに処分されます。

いくつかの例があります。

乱数ジェネレーターの場合、スレッドごとにシードを維持する必要があります。スレッドローカルシードの使用は、各スレッドが他のスレッドとは独立した独自の乱数シーケンスを取得することを意味します。

シードがランダム関数内のローカル変数であった場合、シードを呼び出すたびに初期化され、毎回同じ番号が与えられます。グローバルであれば、スレッドは互いのシーケンスを妨害します。

もう1つの例は、strtokのようなもので、トークン化状態はスレッド固有の基準で格納されています。そうすれば、単一のスレッドは、他のスレッドがトークン化作業を失敗させることなく、strtokへの複数の呼び出しで状態を維持することができます。これにより基本的にstrtok_r(スレッドセーフバージョン)が冗長化されます。

これらの両方の例では、スレッドローカル変数が内に存在するようにしています。の関数を使用しています。プリスレッドされたコードでは、関数内の静的な記憶期間変数になります。スレッドの場合、ローカルストレージの期間をスレッド化するように変更されています。

さらに別の例は、errnoのようなものです。あなたの呼び出しの1つが失敗した後、変数をチェックする前に別のスレッドをerrnoに変更する必要はありませんが、スレッドごとに1つのコピーしか必要ありません。

This siteには、異なる保存期間指定子についての合理的な説明があります。

+5

+1 'strtok'の提案! –

+3

スレッドローカルを使用しても、 'strtok'の問題は解決されません。 'strtok'はシングルスレッド環境でも壊れています。 –

+4

申し訳ありませんが、私はそれを言い直しましょう。これはstrtokに_new_の問題を導入しません:-) – paxdiablo

13

スレッドローカルストレージは、静的(=グローバル)ストレージのようにあらゆる面にあり、各スレッドにはオブジェクトの個別のコピーがあります。オブジェクトの寿命は、スレッド開始時(グローバル変数の場合)または最初の初期化時(ブロックローカル統計の場合)に開始され、スレッドが終了すると終了します(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; 
    } 
}; 
73

変数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; 
} 

void do_nothing(){} 

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

このプログラムには、メインスレッドと手動で作成されたスレッドの2つのスレッドがあります。いずれのスレッドもfを呼び出さないため、thread_localオブジェクトは決して使用されません。したがって、コンパイラがmy_classのインスタンス0,1または2を構築するかどうかは不明です。出力は ""、 "hellohellogoodbyegoodbye"または "hellogoodbye"です。

+0

変数のスレッドローカルコピーは、変数の新しく初期化されたコピーであることに注意することが重要です。つまり、 'threadFunc'の先頭に' g() '呼び出しを追加すると、出力は' 0304029'や '02'、' 03'、 '04'の組の他の順列になります。つまり、スレッドが作成される前に9がiに割り当てられていても、スレッドは新しく構築された 'i'の' i = 0'のコピーを取得します。 'i'が' thread_local int i = random_integer() 'で割り当てられると、各スレッドは新しいランダムな整数を取得します。 –

関連する問題