Czy mój kompilator zignorował mojego nieużywanego statycznego członka klasy thread_local?

10

Chcę dokonać rejestracji wątku w mojej klasie, więc postanawiam dodać opcję dla tej thread_localfunkcji:

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

Kod jest prosty. Moja Barklasa ma statyczny thread_localelement foo. Jeśli thread_local Foo footworzony jest statyczny , oznacza to, że tworzony jest wątek.

Ale kiedy uruchamiam kod, nic nie ma na Foo()wydrukach, a jeśli usunę komentarz w Barkonstruktorze, który używa foo, kod działa poprawnie.

Próbowałem tego na GCC (7.4.0) i Clang (6.0.0) i wyniki są takie same. Wydaje mi się, że kompilator odkrył, że foojest nieużywany i nie generuje instancji. Więc

  1. Czy kompilator zignorował static thread_localczłonka? Jak mogę to debugować?
  2. Jeśli tak, to dlaczego normalny staticczłonek nie ma tego problemu?
Ravenisadesk
źródło

Odpowiedzi:

9

Nie ma problemu z twoją obserwacją. [basic.stc.static] / 2 zabrania eliminowania zmiennych o statycznym czasie przechowywania:

Jeśli zmienna o statycznym czasie przechowywania ma inicjalizację lub destruktor z efektami ubocznymi, nie zostanie wyeliminowana, nawet jeśli wydaje się nieużywana, z wyjątkiem tego, że obiekt klasy lub jego kopia / ruch mogą zostać wyeliminowane zgodnie z [class.copy] .

To ograniczenie nie występuje w przypadku innych okresów przechowywania. W rzeczywistości [basic.stc.thread] / 2 mówi:

Zmienna z czasem przechowywania nici jest inicjalizowana przed jej pierwszym użyciem odr i, jeśli jest zbudowana , zostanie zniszczona przy wyjściu z nici.

Sugeruje to, że zmienna z czasem przechowywania wątku nie musi być konstruowana, chyba że użyto odr.


Ale dlaczego ta rozbieżność?

Dla statycznego czasu przechowywania istnieje tylko jedna instancja zmiennej na program. Skutki uboczne ich budowy mogą być znaczące (podobnie jak konstruktor programowy), więc efekty uboczne są wymagane.

Jednak w przypadku czasu lokalnego przechowywania wątków istnieje problem: algorytm może uruchomić wiele wątków. W przypadku większości tych wątków zmienna jest całkowicie nieistotna. Byłoby zabawne, gdyby wywoływana zewnętrzna biblioteka symulacji fizyki std::reduce(std::execution::par_unseq, first, last)tworzyła wiele fooinstancji, prawda?

Oczywiście może być uzasadnione wykorzystanie efektów ubocznych konstrukcji zmiennych czasu przechowywania lokalnego wątku, które nie są używane odr (np. Narzędzie do śledzenia wątków). Jednak korzyść z zagwarantowania tego nie jest wystarczająca, aby zrekompensować wyżej wspomnianą wadę, więc te zmienne można wyeliminować, o ile nie są używane odr. (Twój kompilator może jednak tego nie robić. Możesz też utworzyć własne opakowanie, std::threadktóre się tym zajmie).

LF
źródło
1
Ok ... jeśli standard tak mówi, niech tak będzie ... Ale czy to nie dziwne, że komitet nie uważa efektów ubocznych za czas przechowywania statycznego?
ravenisadesk,
@reavenisadesk Zobacz zaktualizowaną odpowiedź.
LF,
1

Znalazłem te informacje w „ ELF Handling For Thread-Local Storage ”, który może potwierdzić odpowiedź @LF

Ponadto obsługa w czasie wykonywania powinna unikać tworzenia lokalnego magazynu wątków, jeśli nie jest to konieczne. Na przykład załadowany moduł może być używany tylko przez jeden wątek z wielu, które składają się na proces. Marnowanie pamięci dla wszystkich wątków byłoby stratą pamięci i czasu. Potrzebna jest leniwa metoda. Nie stanowi to większego obciążenia, ponieważ wymaganie do obsługi dynamicznie ładowanych obiektów wymaga już rozpoznania pamięci, która nie została jeszcze przydzielona. Jest to jedyna alternatywa dla zatrzymania wszystkich wątków i przydzielenia pamięci dla wszystkich wątków przed ich ponownym uruchomieniem.

Ravenisadesk
źródło