Mon compilateur a-t-il ignoré mon membre statique inutilisé de la classe thread_local?

10

Je veux faire un enregistrement de thread dans ma classe, donc je décide d'ajouter une vérification pour la thread_localfonctionnalité:

#include <iostream>
#include <thread>

class Foo {
 public:
  Foo() {
    std::cout << "Foo()" << std::endl;
  }
  ~Foo() {
    std::cout << "~Foo()" << std::endl;
  }
};

class Bar {
 public:
  Bar() {
    std::cout << "Bar()" << std::endl;
    //foo;
  }
  ~Bar() {
    std::cout << "~Bar()" << std::endl;
  }
 private:
  static thread_local Foo foo;
};

thread_local Foo Bar::foo;

void worker() {
  {
    std::cout << "enter block" << std::endl;
    Bar bar1;
    Bar bar2;
    std::cout << "exit block" << std::endl;
  }
}

int main() {
  std::thread t1(worker);
  std::thread t2(worker);
  t1.join();
  t2.join();
  std::cout << "thread died" << std::endl;
}

Le code est simple. Ma Barclasse a un thread_localmembre statique foo. Si une statique thread_local Foo fooest créée, cela signifie qu'un thread est créé.

Mais lorsque j'exécute le code, rien dans les Foo()impressions, et si je supprime le commentaire dans Barle constructeur de, qui utilise foo, le code fonctionne correctement.

J'ai essayé cela sur GCC (7.4.0) et Clang (6.0.0) et les résultats sont les mêmes. Je suppose que le compilateur a découvert qu'il foon'est pas utilisé et ne génère pas d'instance. Donc

  1. Le compilateur a-t-il ignoré le static thread_localmembre? Comment puis-je déboguer pour cela?
  2. Si oui, pourquoi un staticmembre normal n'a-t-il pas ce problème?
ravenisadesk
la source

Réponses:

9

Il n'y a aucun problème avec votre observation. [basic.stc.static] / 2 interdit d'éliminer les variables avec une durée de stockage statique:

Si une variable avec une durée de stockage statique a une initialisation ou un destructeur avec des effets secondaires, elle ne doit pas être éliminée même si elle semble inutilisée, sauf qu'un objet de classe ou sa copie / déplacement peuvent être éliminés comme spécifié dans [class.copy] .

Cette restriction n'est pas présente pour les autres durées de stockage. En fait, [basic.stc.thread] / 2 dit:

Une variable avec une durée de stockage de thread doit être initialisée avant sa première utilisation odr et, si elle est construite , doit être détruite à la sortie du thread.

Cela suggère qu'une variable avec la durée de stockage des threads n'a pas besoin d'être construite à moins qu'elle ne soit utilisée.


Mais pourquoi cet écart?

Pour la durée de stockage statique, il n'y a qu'une seule instance d'une variable par programme. Les effets secondaires de sa construction peuvent être importants (un peu comme un constructeur à l'échelle du programme), donc les effets secondaires sont nécessaires.

Cependant, pour la durée de stockage local des threads, il y a un problème: un algorithme peut démarrer beaucoup de threads. Pour la plupart de ces threads, la variable est complètement hors de propos. Ce serait hilarant si une bibliothèque de simulation physique externe qui appelle std::reduce(std::execution::par_unseq, first, last)finit par créer beaucoup d' fooinstances, non?

Bien sûr, il peut y avoir une utilisation légitime pour les effets secondaires de la construction de variables de durée de stockage local de threads qui ne sont pas utilisées (par exemple, un tracker de threads). Cependant, l'avantage de garantir cela n'est pas suffisant pour compenser l'inconvénient susmentionné, de sorte que ces variables peuvent être éliminées tant qu'elles ne sont pas utilisées de manière irrégulière. (Cependant, votre compilateur peut choisir de ne pas le faire. Et vous pouvez également créer votre propre wrapper std::threadqui s'en occupe.)

LF
la source
1
D'accord ... si la norme le dit, alors tant pis ... Mais n'est-ce pas bizarre que le comité ne considère pas les effets secondaires comme une durée de stockage statique?
ravenisadesk
@reavenisadesk Voir la réponse mise à jour.
LF
1

J'ai trouvé ces informations dans " ELF Handling For Thread-Local Storage " qui peut prouver la réponse de @LF

De plus, la prise en charge au moment de l'exécution doit éviter de créer le stockage local de threads si cela n'est pas nécessaire. Par exemple, un module chargé ne peut être utilisé que par un seul thread parmi les nombreux qui composent le processus. Ce serait une perte de mémoire et de temps d'allouer le stockage pour tous les threads. Une méthode paresseuse est souhaitée. Ce n'est pas beaucoup de charge supplémentaire, car la nécessité de gérer des objets chargés dynamiquement nécessite déjà de reconnaître le stockage qui n'est pas encore alloué. C'est la seule alternative à l'arrêt de tous les threads et à l'allocation de stockage pour tous les threads avant de les laisser s'exécuter à nouveau.

ravenisadesk
la source