Информация об изменениях

Сообщение Re: thread_local in C++17 от 01.02.2023 9:53

Изменено 01.02.2023 9:57 vopl

Re: thread_local in C++17
Здравствуйте, Videoman, Вы писали:

V>Код вычистить пока не удалось, так что попробую пока своими словами. Появилась следующая проблема с thread_local объектом:

V>Если некий std::map, который объявлен как static в одном из С++ файлов. Также есть thread_local объект — регистратор, который всё что делает, в своём конструкторе регистрирует себя в static std::map, а в деструкторе разрегистрирует. Схема примерно такая:
V>
V>[thread_local thread1_reg] --> [static std::map storage] <-- [thread_local thread2_reg]
V>

V>В msvc- компилирует всё как задумано, вызывает конструкторы/деструкторы thread_local объекта в каждом потоке.
V>g++ и clang — вызывают конструктор/деструктор thread_local регистратора только для основного потока с main, во всех остальных потоках тишина.

V>У меня есть только одна догадка, что так как служебные потоки не обращаются непосредственно к thread_local объекту напрямую, а только к static мапе, линкеры выкидывают, по их мнению, не создающих внешнего эффекта конструкторы/деструкторы регистраторов.


V>Вопросы:

V>- прав ли я в своих догадках, или это что-то другое?
V>- как починить?
V>- может появилось что-то в С++17 для таких случаев, атрибуты там какие-нибудь или что-то похожее?

V>P.S. только что глянул ассебмлер: gcc реально создает объект в TLS только при первом обращении к thread_local объекту. Это так и должно быть? Можно ли как-то исхитрится и создавать объект как это делает "Студия", при запуске потока?


Похоже, и ms gcc/clang — оба ведут себя правильно.

https://timsong-cpp.github.io/cppwp/basic.stc.thread
thread_local определяет что место под объект выделяется в некоем стораже на каждый поток блабла
а инициализация объекта это отдельная песня. Судя по всему у тебя объект с нетривиальным конструктором, тогда это dynamic initialization

https://timsong-cpp.github.io/cppwp/basic.start.dynamic#7
реализация сама выбирает когда ей инициализировать такие объекты, то ли сразу то ли отложенно до первого odr-use. Судя по всему, ms делает сразу, а gcc/clang отложенно.

То есть, твоя догадка практически верна, любой odr-use в потоке требует наличия проинициализированного объекта, и компилятор это требование обеспечивает по факту odr-use

Как починить — хз, но плясать надо от того что надо как то сделать odr-use этого объекта в том потоке в котором он нужен.. Эта проблема несколько похожа на проблему динамических десериализаторов, у них из сети прилетают данные, из них десериализатор берет идентификатор и на его основе из мапы выбирает нужный процессор, далее отдает ему остаток данных. Вот их проблема — как сделать само-регистрирующийся в десериализаторе процессор. То есть, чтобы программист только написал код этого процессора, а он чик-чик сам бы в мапу записался. Там это решают на уровне static storage duration, а он инициализируется если есть побочные эффекты в конструкторе. Это легко обеспечить. В случае же thread_local внутренние побочные эффекты не уситываются для старта инициализации, нужно обязательно проявить активность снаружи объекта, сделать ему odr-use. И я так понимаю, что вариант явно ручками каждый такой thread_local потрогать — вовсе не вариант. Хз как это можно автоматизировать
Re: thread_local in C++17
Здравствуйте, Videoman, Вы писали:

V>Код вычистить пока не удалось, так что попробую пока своими словами. Появилась следующая проблема с thread_local объектом:

V>Если некий std::map, который объявлен как static в одном из С++ файлов. Также есть thread_local объект — регистратор, который всё что делает, в своём конструкторе регистрирует себя в static std::map, а в деструкторе разрегистрирует. Схема примерно такая:
V>
V>[thread_local thread1_reg] --> [static std::map storage] <-- [thread_local thread2_reg]
V>

V>В msvc- компилирует всё как задумано, вызывает конструкторы/деструкторы thread_local объекта в каждом потоке.
V>g++ и clang — вызывают конструктор/деструктор thread_local регистратора только для основного потока с main, во всех остальных потоках тишина.

V>У меня есть только одна догадка, что так как служебные потоки не обращаются непосредственно к thread_local объекту напрямую, а только к static мапе, линкеры выкидывают, по их мнению, не создающих внешнего эффекта конструкторы/деструкторы регистраторов.


V>Вопросы:

V>- прав ли я в своих догадках, или это что-то другое?
V>- как починить?
V>- может появилось что-то в С++17 для таких случаев, атрибуты там какие-нибудь или что-то похожее?

V>P.S. только что глянул ассебмлер: gcc реально создает объект в TLS только при первом обращении к thread_local объекту. Это так и должно быть? Можно ли как-то исхитрится и создавать объект как это делает "Студия", при запуске потока?


Похоже, и ms и gcc/clang — оба ведут себя правильно.

https://timsong-cpp.github.io/cppwp/basic.stc.thread
thread_local определяет что место под объект выделяется в некоем стораже на каждый поток блабла
а инициализация объекта это отдельная песня. Судя по всему у тебя объект с нетривиальным конструктором, тогда это dynamic initialization

https://timsong-cpp.github.io/cppwp/basic.start.dynamic#7
реализация сама выбирает когда ей инициализировать такие объекты, то ли сразу то ли отложенно до первого odr-use. Судя по всему, ms делает сразу, а gcc/clang отложенно.

То есть, твоя догадка практически верна, любой odr-use в потоке требует наличия проинициализированного объекта, и компилятор это требование обеспечивает по факту odr-use

Как починить — хз, но плясать надо от того что надо как то сделать odr-use этого объекта в том потоке в котором он нужен.. Эта проблема несколько похожа на проблему динамических десериализаторов, у них из сети прилетают данные, из них десериализатор берет идентификатор и на его основе из мапы выбирает нужный процессор, далее отдает ему остаток данных. Вот их проблема — как сделать само-регистрирующийся в десериализаторе процессор. То есть, чтобы программист только написал код этого процессора, а он чик-чик сам бы в мапу записался. Там это решают на уровне static storage duration, а он инициализируется если есть побочные эффекты в конструкторе. Это легко обеспечить. В случае же thread_local внутренние побочные эффекты не уситываются для старта инициализации, нужно обязательно проявить активность снаружи объекта, сделать ему odr-use. И я так понимаю, что вариант явно ручками каждый такой thread_local потрогать в нужном потоке чтобы получилось odr-use — вовсе не вариант. Хз как это можно автоматизировать