Lorsque vous déclarez une variable thread_local
alors chaque thread a sa propre copie. Lorsque vous y faites référence par son nom, c'est la copie associée au fil d'exécution actuel qui est utilisée, par exemple
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;
}
Ce code produira "2349", "3249", "4239", "4329", "2439" ou "3429", mais jamais autre chose. Chaque thread a sa propre copie de i
qui est affecté, incrémenté et ensuite imprimé. Le fil d'exécution main
a aussi sa propre copie, qui est assignée au début et laissée inchangée. Ces copies sont totalement indépendantes, et chacune a une adresse différente.
C'est seulement le nom qui est spécial à cet égard --- si vous prenez l'adresse d'un thread_local
alors vous avez juste un pointeur normal vers un objet normal, que vous pouvez librement faire passer entre les threads. par ex.
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;
}
Puisque l'adresse de i
est transmis à la fonction thread, alors la copie de i
appartenant au thread principal peut être assignée même si elle est thread_local
. Ce programme produira donc "42". Si vous faites cela, vous devez alors faire attention à ce que *p
n'est pas accédé après que le thread auquel il appartient soit sorti, sinon vous obtenez un pointeur suspendu et un comportement indéfini comme dans tout autre cas où l'objet pointé est détruit.
thread_local
sont initialisées "avant la première utilisation", donc si elles ne sont jamais touchées par un thread donné, elles ne sont pas nécessairement initialisées. Ceci permet aux compilateurs d'éviter de construire chaque variable thread_local
dans le programme pour un fil qui est entièrement autonome et ne touche aucun d'entre eux, par exemple
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();
}
Dans ce programme, il y a 2 threads : le thread principal et le thread créé manuellement. Aucun des deux threads n'appelle f
donc le thread_local
n'est jamais utilisé. Il n'est donc pas précisé si le compilateur construira 0, 1 ou 2 instances de l'objet my_class
et la sortie peut être "", "hellologoodbyegoodbye" ou "hellogoodbye".