Il mio compilatore ha ignorato il mio membro di classe thread_local statico inutilizzato?


10

Voglio fare una registrazione di thread nella mia classe, quindi decido di aggiungere un controllo per la thread_localfunzione:

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

Il codice è semplice La mia Barclasse ha un thread_localmembro statico foo. Se thread_local Foo fooviene creato uno statico , significa che viene creato un thread.

Ma quando eseguo il codice, niente nelle Foo()stampe, e se rimuovo il commento nel Barcostruttore di, che usa foo, il codice funziona bene.

Ho provato questo su GCC (7.4.0) e Clang (6.0.0) e i risultati sono gli stessi. Immagino che il compilatore abbia scoperto che foonon è in uso e non genera un'istanza. Così

  1. Il compilatore ha ignorato il static thread_localmembro? Come posso eseguire il debug per questo?
  2. In tal caso, perché un staticmembro normale non ha questo problema?

Risposte:


9

Non ci sono problemi con la tua osservazione. [basic.stc.static] / 2 vieta l'eliminazione delle variabili con durata della memoria statica:

Se una variabile con durata di memorizzazione statica ha inizializzazione o un distruttore con effetti collaterali, non deve essere eliminata anche se sembra essere inutilizzata, tranne per il fatto che un oggetto di classe o la sua copia / spostamento possono essere eliminati come specificato in [class.copy] .

Questa restrizione non è presente per altre durate di conservazione. In effetti, [basic.stc.thread] / 2 dice:

Una variabile con durata di conservazione del filo deve essere inizializzata prima del suo primo utilizzo e, se costruita , deve essere distrutta all'uscita del filo.

Ciò suggerisce che non è necessario costruire una variabile con durata della memorizzazione del thread a meno che non venga utilizzato odr.


Ma perché questa discrepanza?

Per la durata della memoria statica, esiste una sola istanza di una variabile per programma. Gli effetti collaterali della sua costruzione possono essere significativi (un po 'come un costruttore a livello di programma), quindi gli effetti collaterali sono richiesti.

Per la durata della memorizzazione locale dei thread, tuttavia, esiste un problema: un algoritmo può avviare molti thread. Per la maggior parte di questi thread, la variabile è completamente irrilevante. Sarebbe esilarante se una libreria di simulazione di fisica esterna che chiama std::reduce(std::execution::par_unseq, first, last)finisce per creare molte fooistanze, giusto?

Naturalmente, può esserci un uso legittimo per gli effetti collaterali della costruzione di variabili della durata della memoria locale del thread che non vengono utilizzate in modo anomalo (ad esempio, un tracker di thread). Tuttavia, il vantaggio di garantire ciò non è sufficiente per compensare il suddetto inconveniente, quindi è possibile eliminare queste variabili purché non vengano utilizzate in modo anomalo. (Il compilatore può scegliere di non farlo, però. E puoi anche creare il tuo wrapper std::threadche si occupa di questo.)


1
Va bene ... se lo standard lo dicesse, allora sia così ... Ma non è strano che il comitato non consideri gli effetti collaterali come durata di memorizzazione statica?
Ravenisadesk,

@reavenisadesk Vedi risposta aggiornata.
LF

1

Ho trovato queste informazioni in " Gestione ELF per archiviazione locale thread " che può dimostrare la risposta di @LF

Inoltre, il supporto di runtime dovrebbe evitare di creare l'archivio locale thread se non è necessario. Ad esempio, un modulo caricato potrebbe essere utilizzato solo da un thread tra i molti che compongono il processo. Sarebbe uno spreco di memoria e tempo per allocare la memoria per tutti i thread. Si desidera un metodo pigro. Questo non è un onere aggiuntivo poiché il requisito di gestire oggetti caricati dinamicamente richiede già di riconoscere un archivio non ancora allocato. Questa è l'unica alternativa all'arresto di tutti i thread e all'allocazione dell'archiviazione per tutti i thread prima di lasciarli funzionare di nuovo.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.