Мой компилятор игнорировал мой неиспользуемый статический член класса thread_local?

10

Я хочу сделать некоторую регистрацию потока в моем классе, поэтому я решил добавить проверку для этой thread_localфункции:

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

Код прост. У моего Barкласса есть статический thread_localчлен foo. Если статика thread_local Foo fooсоздана, это означает, что создан поток.

Но когда я запускаю код, ничего не Foo()печатается, и если я удаляю комментарий в Barконструкторе, который использует foo, код работает нормально.

Я попробовал это на GCC (7.4.0) и Clang (6.0.0), и результаты совпадают. Я предполагаю, что компилятор обнаружил, что fooон не используется и не создает экземпляр. Так

  1. Компилятор игнорировал static thread_localчлен? Как я могу отладить для этого?
  2. Если так, то почему у нормального staticчлена нет этой проблемы?
ravenisadesk
источник

Ответы:

9

Там нет проблем с вашим наблюдением. [basic.stc.static] / 2 запрещает исключать переменные со статической продолжительностью хранения:

Если переменная со статической продолжительностью хранения имеет инициализацию или деструктор с побочными эффектами, она не должна быть удалена, даже если она не используется, за исключением того, что объект класса или его копирование / перемещение могут быть удалены, как указано в [class.copy] ,

Это ограничение отсутствует для других сроков хранения. Фактически, [basic.stc.thread] / 2 говорит:

Переменная с продолжительностью хранения потока должна быть инициализирована перед ее первым использованием odr и, если она создана , должна быть уничтожена при выходе из потока.

Это говорит о том, что переменная с продолжительностью хранения потока не должна создаваться, если не используется odr.


Но почему это несоответствие?

Для статической длительности хранения существует только один экземпляр переменной для каждой программы. Побочные эффекты их создания могут быть значительными (вроде конструктора всей программы), поэтому побочные эффекты являются обязательными.

Однако для продолжительности локального хранения потока существует проблема: алгоритм может запускать много потоков. Для большинства из этих потоков переменная совершенно не имеет значения. Было бы весело, если бы библиотека имитации внешней физики, которая вызывает, в std::reduce(std::execution::par_unseq, first, last)конечном итоге создала много fooэкземпляров, верно?

Конечно, может быть законное использование побочных эффектов построения переменных продолжительности локального хранения потока, которые не используются в odr (например, трекер потока). Однако преимущества для гарантии этого недостаточно, чтобы компенсировать вышеупомянутый недостаток, поэтому эти переменные могут быть исключены, если они не используются odr. (Тем не менее, ваш компилятор может отказаться от этого. И вы также можете создать свою собственную оболочку, std::threadкоторая позаботится об этом.)

LF
источник
1
Хорошо ... если в стандарте так сказано, то так и будет ... Но не странно ли, что комитет не рассматривает побочные эффекты как продолжительность статического хранения?
Равенисадеск
@reavenisadesk Смотрите обновленный ответ.
LF
1

Я нашел эту информацию в " Обработка ELF для локального хранилища потоков ", которая может доказать ответ @LF

Кроме того, поддержка времени выполнения должна избегать создания локального хранилища потока, если в этом нет необходимости. Например, загруженный модуль может использоваться только одним потоком из множества, составляющих процесс. Было бы напрасной тратой памяти и времени выделить место для всех потоков. Нужен ленивый метод. Это не является большой дополнительной нагрузкой, поскольку требование обработки динамически загружаемых объектов уже требует распознавания хранилища, которое еще не выделено. Это единственная альтернатива остановке всех потоков и выделению хранилища для всех потоков перед тем, как снова запустить их.

ravenisadesk
источник