153

thread_localC++11での説明と混同しています。私の理解では、各スレッドには関数内のローカル変数の一意のコピーがあります。グローバル/静的変数には、すべてのスレッドからアクセスできます(ロックを使用した同期アクセスの可能性があります)。そして、thread_local変数はすべてのスレッドに表示されますが、変数が定義されているスレッドによってのみ変更できますか?それが正しいか?

4

3 に答える 3

185

スレッドローカルストレージ期間は、(それを使用する関数の観点から)グローバルまたは静的ストレージ期間のように見えるデータを指すために使用される用語ですが、実際には、スレッドごとに1つのコピーがあります。

これは、現在の自動(ブロック/機能中に存在する)、静的(プログラム期間中に存在する)、および動的(割り当てと割り当て解除の間のヒープに存在する)に追加されます。

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

以下にいくつかの例を示します。

シードをスレッドごとに維持する必要がある乱数ジェネレーターについて考えてみてください。スレッドローカルシードを使用するということは、他のスレッドとは関係なく、各スレッドが独自の乱数シーケンスを取得することを意味します。

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

別の例はstrtok、トークン化状態がスレッド固有に格納されるようなものです。そうすれば、単一のスレッドは、他のスレッドがトークン化の努力を台無しにしないことを保証できますが、それでも複数の呼び出しにわたって状態を維持することができますstrtok-これは基本的にstrtok_r(スレッドセーフバージョン)を冗長にします。

これらの例は両方とも、スレッドローカル変数がそれを使用する関数内に存在することを可能にします。事前にスレッド化されたコードでは、それは単に関数内の静的ストレージ期間変数になります。スレッドの場合、これはスレッドローカルストレージ期間に変更されます。

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

このサイトには、さまざまなストレージ期間指定子の合理的な説明があります。

于 2012-08-16T09:13:22.620 に答える
164

When you declare a variable thread_local then each thread has its own copy. When you refer to it by name, then the copy associated with the current thread is used. e.g.

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

This code will output "2349", "3249", "4239", "4329", "2439" or "3429", but never anything else. Each thread has its own copy of i, which is assigned to, incremented and then printed. The thread running main also has its own copy, which is assigned to at the beginning and then left unchanged. These copies are entirely independent, and each has a different address.

It is only the name that is special in that respect --- if you take the address of a thread_local variable then you just have a normal pointer to a normal object, which you can freely pass between threads. e.g.

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

Since the address of i is passed to the thread function, then the copy of i belonging to the main thread can be assigned to even though it is thread_local. This program will thus output "42". If you do this, then you need to take care that *p is not accessed after the thread it belongs to has exited, otherwise you get a dangling pointer and undefined behaviour just like any other case where the pointed-to object is destroyed.

thread_local variables are initialized "before first use", so if they are never touched by a given thread then they are not necessarily ever initialized. This is to allow compilers to avoid constructing every thread_local variable in the program for a thread that is entirely self-contained and doesn't touch any of them. e.g.

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();
}

In this program there are 2 threads: the main thread and the manually-created thread. Neither thread calls f, so the thread_local object is never used. It is therefore unspecified whether the compiler will construct 0, 1 or 2 instances of my_class, and the output may be "", "hellohellogoodbyegoodbye" or "hellogoodbye".

于 2012-08-16T10:41:50.240 に答える
25

Thread-local storage is in every aspect like static (= global) storage, only that each thread has a separate copy of the object. The object's life time starts either at thread start (for global variables) or at first initialization (for block-local statics), and ends when the thread ends (i.e. when join() is called).

Consequently, only variables that could also be declared static may be declared as thread_local, i.e. global variables (more precisely: variables "at namespace scope"), static class members, and block-static variables (in which case static is implied).

As an example, suppose you have a thread pool and want to know how well your work load was being balanced:

thread_local Counter c;

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

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

This would print thread usage statistics, e.g. with an implementation like this:

struct Counter
{
     unsigned int c = 0;
     void increment() { ++c; }
     ~Counter()
     {
         std::cout << "Thread #" << std::this_thread::id() << " was called "
                   << c << " times" << std::endl;
     }
};
于 2012-08-16T09:23:44.920 に答える