А.8. Поточно-локальные переменные

У поточно-локальной переменной имеется отдельный экземпляр в каждом потоке программы. Для объявления поточно-локальной переменной служит ключевое слово thread_local. Поточно-локальными могут быть переменные с областью видимости пространства имен, статические члены классов и локальные переменные. Говорят, что они имеют потоковое время жизни (thread storage duration):

thread_local int x;←┐ Поточно-локальная переменная в

                    │ области видимости пространства

                    │ имен

class X                             │ Поточно-локальная

{                                   │ статическая пере-

 static thread_local std::string s;←┘ менная-член класса

};

                                      │ Необходимо

static thread_local std::string X::s;определение X::s

void foo() {

                                  │ Поточно-локальная

 thread_local std::vector<int> v;←┘ локальная переменная

}

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

Поточно-локальные переменные, объявленные внутри функции, инициализируются, когда поток управления впервые проходит через объявление переменной в данном потоке. Если функция в данном потоке не вызывалась, то объявленные в ней поточно-локальные переменные не будут сконструированы. Точно такое же поведение характерно для локальных статических переменных, только в этом случае оно применяется в каждом потоке по отдельности.

У поточно-локальных переменных есть и другие общие черты со статическими переменными — они инициализируются нулями перед последующей инициализацией (например, динамической) и, если конструктор поточно-локальной переменной возбуждает исключение, то вызывается функция std::terminate(), которая аварийно завершает приложение.

Деструкторы всех поточно-локальных переменных, сконструированных в данном потоке, вызываются после возврата из функции потока в порядке, обратном конструированию. Поскольку порядок инициализации не определён, то необходимо гарантировать отсутствие взаимозависимостей между деструкторами таких переменных. Если деструктор поточно-локальной переменной возбуждает исключение, то вызывается функция std::terminate(), как и при конструировании.

Поточно-локальные переменные, принадлежащие некоторому потоку, уничтожаются также в случае, когда данный поток вызывает std::exit() или возвращается из main() (что эквивалентно вызову std::exit() со значением, которое вернула main()). Если другие потоки продолжают работать, когда приложение завершается, то деструкторы принадлежащих им поточно-локальных переменных не вызываются.

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