La durée de stockage locale du thread est un terme utilisé pour désigner des données qui sont apparemment une durée de stockage globale ou statique (du point de vue des fonctions qui l'utilisent) mais en réalité, il y a une copie par thread.
Il s'ajoute au courant automatique (existe pendant un bloc / fonction), statique (existe pour la durée du programme) et dynamique (existe sur le tas entre l'allocation et la désallocation).
Quelque chose qui est local au thread est mis en place lors de la création du thread et éliminé lorsque le thread s'arrête.
Quelques exemples suivent.
Pensez à un générateur de nombres aléatoires où la graine doit être maintenue sur une base par thread. L'utilisation d'une graine locale au thread signifie que chaque thread obtient sa propre séquence de nombres aléatoires, indépendante des autres threads.
Si votre valeur de départ était une variable locale dans la fonction aléatoire, elle serait initialisée à chaque fois que vous l'appeliez, vous donnant le même nombre à chaque fois. S'il s'agissait d'un global, les threads interféreraient les uns avec les autres.
Un autre exemple est quelque chose comme strtok
où l'état de tokenisation est stocké sur une base spécifique au thread. De cette façon, un seul thread peut être sûr que d'autres threads ne gâcheront pas ses efforts de tokenisation, tout en étant capable de maintenir l'état sur plusieurs appels à strtok
- cela rend fondamentalement strtok_r
(la version thread-safe) redondante.
Ces deux exemples permettent à la variable locale de thread d'exister dans la fonction qui l'utilise. Dans le code pré-threadé, il s'agirait simplement d'une variable de durée de stockage statique dans la fonction. Pour les threads, cela est modifié pour tenir compte de la durée de stockage local des threads.
Encore un autre exemple serait quelque chose comme errno
. Vous ne voulez pas que des threads séparés soient modifiés errno
après l'échec de l'un de vos appels, mais avant de pouvoir vérifier la variable, et pourtant vous ne voulez qu'une copie par thread.
Ce site a une description raisonnable des différents spécificateurs de durée de stockage.
strtok
.strtok
est cassé même dans un environnement à thread unique.r
signifie «rentrant», ce qui n'a rien à voir avec la sécurité des fils. Il est vrai que vous pouvez faire fonctionner certaines choses en toute sécurité avec le stockage local des threads, mais vous ne pouvez pas les faire rentrer.strtok
appeler d'autres fonctions.while (something) { char *next = strtok(whatever); someFunction(next); // someFunction calls strtok }
Lorsque vous déclarez une variable,
thread_local
chaque thread a sa propre copie. Lorsque vous y faites référence par son nom, la copie associée au thread actuel est utilisée. par exempleCe code affichera "2349", "3249", "4239", "4329", "2439" ou "3429", mais jamais rien d'autre. Chaque fil a sa propre copie de
i
, qui est assignée, incrémentée puis imprimée. Le fil en coursmain
également sa propre copie, qui est affectée au début et laissée inchangée. Ces copies sont entièrement indépendantes et ont chacune une adresse différente.C'est seulement le nom qui est spécial à cet égard --- si vous prenez l'adresse d'une
thread_local
variable, vous avez juste un pointeur normal vers un objet normal, que vous pouvez passer librement entre les threads. par exempleDepuis l'adresse de
i
est passée à la fonction de thread, alors la copie de l'i
appartenance au thread principal peut être affectée même si elle l'estthread_local
. Ce programme affichera donc "42". Si vous faites cela, vous devez faire attention à*p
ne pas y accéder après la sortie du thread auquel il appartient, 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
les variables 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 jamais initialisées. Cela permet aux compilateurs d'éviter de construire toutes lesthread_local
variables du programme pour un thread entièrement autonome et qui ne touche à aucune d'elles. par exempleDans ce programme, il y a 2 threads: le thread principal et le thread créé manuellement. Aucun des threads n'appelle
f
, donc l'thread_local
objet n'est jamais utilisé. Il n'est donc pas spécifié si le compilateur construira 0, 1 ou 2 instances demy_class
, et la sortie peut être "", "hellohellogoodbyegoodbye" ou "hellogoodbye".la source
g()
appel au débutthreadFunc
, la sortie sera0304029
ou une autre permutation des paires02
,03
et04
. Autrement dit, même si 9 est affecté ài
avant la création des threads, les threads obtiennent une copie fraîchement construite dei
wherei=0
. Sii
est assigné avecthread_local int i = random_integer()
, alors chaque thread obtient un nouvel entier aléatoire.02
,03
,04
, il peut y avoir d' autres séquences comme020043
Le stockage local de thread est dans tous les aspects comme le stockage statique (= global), seulement que chaque thread a une copie distincte de l'objet. La durée de vie de l'objet commence soit au début du thread (pour les variables globales) soit à la première initialisation (pour la statique locale au bloc), et se termine lorsque le thread se termine (c'est-à-dire quand
join()
est appelé).Par conséquent, seules les variables qui pourraient également être déclarées
static
peuvent être déclarées commethread_local
, c'est-à-dire des variables globales (plus précisément: des variables "à la portée de l'espace de noms"), des membres de classe statiques et des variables statiques de bloc (auquel casstatic
est implicite).À titre d'exemple, supposons que vous ayez un pool de threads et que vous souhaitiez savoir dans quelle mesure votre charge de travail était équilibrée:
Cela afficherait les statistiques d'utilisation des threads, par exemple avec une implémentation comme celle-ci:
la source