Чи проігнорував мій компілятор мого невикористаного статичного учасника класу 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конструкторі 's, який використовує foo, код працює нормально.

Я спробував це на GCC (7.4.0) та Clang (6.0.0), і результати однакові. Я думаю, що компілятор виявив, що fooвін не використовується, і не створює екземпляр. Тому

  1. Чи компілятор ігнорував static thread_localучасника? Як я можу налагодити це?
  2. Якщо так, то чому нормальний staticчлен не має цієї проблеми?

Відповіді:


9

З вашим спостереженням проблем немає. [basic.stc.static] / 2 забороняє виключати змінні зі статичною тривалістю зберігання:

Якщо змінна зі статичною тривалістю зберігання має ініціалізацію або деструктор із побічними ефектами, вона не може бути усунена, навіть якщо вона видається невикористаною, за винятком того, що об’єкт класу або його копія / переміщення може бути усунено, як зазначено в [class.copy] .

Це обмеження не існує для інших термінів зберігання. Насправді, [basic.stc.thread] / 2 говорить:

Змінна з тривалістю зберігання потоку повинна ініціалізуватися до її першого використання та, якщо вона побудована , знищується при виході з потоку.

Це говорить про те, що змінну із тривалістю зберігання потоку не потрібно будувати, якщо не використовується odr.


Але чому це розбіжність?

Для статичної тривалості зберігання існує лише один примірник змінної на програму. Побічні ефекти їх побудови можуть бути суттєвими (начебто на зразок програмового конструктора), тому необхідні побічні ефекти.

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

Звичайно, може бути законним використання побічних ефектів побудови змінних тривалості локального зберігання потоку, які не використовуються (наприклад, трекер потоку). Однак переваги для гарантування цього недостатньо для компенсації вищезазначеного недоліку, тому ці змінні дозволяється усунути до тих пір, поки вони не використовуються. (Ваш компілятор може вирішити цього не робити. І ви також можете зробити власну обгортку навколо, std::threadяка піклується про це.)


1
Гаразд ... якщо стандарт сказав так, то так і не буде ... Але хіба це не дивно, що комітет не вважає побічні ефекти статичною тривалістю зберігання?
ravenisadesk

@reavenisadesk Дивіться оновлену відповідь.
LF

1

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

Крім того, підтримка часу виконання повинна уникати створення локального сховища потоків, якщо це не потрібно. Наприклад, завантажений модуль може використовуватись лише одним потоком з багатьох, які складають процес. Було б марно пам'яті та часу виділити сховище для всіх потоків. Ледачий метод потрібен. Це не набагато додаткового навантаження, оскільки вимога поводження з динамічно завантаженими об'єктами вже вимагає розпізнавання сховища, яке ще не виділено. Це єдина альтернатива зупинці всіх потоків та виділенню пам’яті для всіх потоків перед тим, як дозволити їм знову працювати.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.