С ++ 11 переменные thread_local автоматически статичны?

85

Есть ли разница между этими двумя сегментами кода:

void f() {
    thread_local vector<int> V;
    V.clear();
    ... // use V as a temporary variable
}

и

void f() {
    static thread_local vector<int> V;
    V.clear();
    ... // use V as a temporary variable
}

Предыстория: изначально у меня был статический вектор V (для хранения некоторых промежуточных значений, он очищается каждый раз, когда я вхожу в функцию) и однопоточная программа. Я хочу превратить программу в многопоточную, поэтому мне как-то нужно избавиться от этого статического модификатора. Моя идея - превратить каждую статику в thread_local и не беспокоиться ни о чем другом? Может ли этот подход иметь неприятные последствия?

Зуза
источник
17
thread_localДля начала нет смысла иметь локальную переменную… у каждого потока есть свой собственный стек вызовов.
Конрад Рудольф
1
Изначально несколько функций C были написаны для возврата адреса статических или глобальных переменных. Позже было обнаружено, что это приводило к неясным ошибкам при использовании в многопоточных приложениях (например, errno, localtime). Кроме того, иногда очень вредно защищать общие переменные с помощью мьютекса, когда функция вызывается из нескольких потоков, или когда приходится передавать объект контекста потока среди многих вызываемых объектов и методов. Переменные, которые являются локальными для потока, решают эти и другие вопросы.
edwinc
3
@Konrad Rudolph объявляет только локальные переменные, staticа static thread_localне инициализирует один экземпляр переменной для каждого потока.
Дэвид
1
@davide Дело не в этом, ни во мне, ни в ОП. Мы говорим не о staticvs, static thread_localа скорее о autovs thread_local, используя значение до C ++ 11 auto(т.е. автоматическое хранение).
Конрад Рудольф
1
Также см. Как определить локальные статические переменные потока? . Небольшое замечание юриста по языку ... Поддержка Microsoft и TLS изменилась после Vista; см. Локальное хранилище потоков (TLS) . Это изменение влияет на такие вещи, как Singleton, и может применяться или не применяться. Если вы используете программную модель abondware, то, вероятно, все будет в порядке. Если вам нравится поддерживать несколько компиляторов и платформ, возможно, вам придется обратить на это внимание.
jww 01

Ответы:

94

Согласно стандарту C ++

Когда thread_local применяется к переменной области блока, подразумевается статический спецификатор класса хранения, если он не отображается явно

Значит, это определение

void f() {
    thread_local vector<int> V;
    V.clear();
    ... // use V as a temporary variable
}

эквивалентно

void f() {
    static thread_local vector<int> V;
    V.clear();
    ... // use V as a temporary variable
}

Однако статическая переменная - это не то же самое, что переменная thread_local.

1 Все переменные, объявленные с ключевым словом thread_local, имеют продолжительность хранения потока. Хранение этих объектов должно длиться в течение всего потока, в котором они созданы. Для каждого потока существует отдельный объект или ссылка, и использование объявленного имени относится к сущности, связанной с текущим потоком.

Чтобы различать эти переменные, стандарт вводит новый термин « продолжительность хранения потока» наряду со статической продолжительностью хранения.

Влад из Москвы
источник
1
staticи, externтаким образом, не подразумевают класс хранения, а только связь для переменных thread_local даже во внутренних областях.
Дедупликатор
4
Переменные области видимости блока @Deduplicator не связаны. Значит, ваше резюме неверное. Как я писал в посте, у них есть срок хранения потоков. Фактически это то же самое, что и продолжительность статического хранения, но применяется к каждому потоку.
Влад из Москвы
1
Если вы добавляете extern, вы делаете объявление, а не определение. Так?
Дедупликатор
1
@Deduplicator Это означает, что определения переменных области видимости блока не связаны.
Влад из Москвы
1
Я просто попробовал это в VS 2013, и он кричит: «Переменные TL не могут быть инициализированы динамически». Я озадачен.
v.oddou
19

Да, «локальное хранилище потока» очень похоже на «глобальное» (или «статическое хранилище»), только вместо «длительности всей программы» у вас есть «продолжительность всего потока». Таким образом, локальная переменная потока, локальная для блока, инициализируется при первом прохождении элемента управления через ее объявление, но отдельно в каждом потоке, и уничтожается, когда поток завершается.

Керрек С.Б.
источник
6

При использовании с thread_local, staticподразумевается в области видимости блока (см. Ответ @ Vlad), требуется для члена класса; Я думаю, это означает привязку к области пространства имен.

По 9.2 / 6:

В определении класса член не должен объявляться с помощью спецификатора класса-хранилища thread_local, если он также не объявлен статическим

Чтобы ответить на исходный вопрос:

С ++ 11 переменные thread_local автоматически статичны?

Нет выбора, кроме переменных области пространства имен.

Есть ли разница между этими двумя сегментами кода:

Нет.

Vehsakul
источник
4

Локальное хранилище потоков статично, но ведет себя совсем не так, как простое статическое хранилище.

Когда вы объявляете переменную статической, существует ровно один экземпляр переменной. Система компилятора / времени выполнения гарантирует, что он будет инициализирован для вас когда-нибудь до того, как вы его действительно начнете использовать, без указания точного времени (некоторые детали здесь опущены).

C ++ 11 гарантирует, что эта инициализация будет потокобезопасной, однако до C ++ 11 эта безопасность потока не была гарантирована. Например

static X * pointer = new X;

может привести к утечке экземпляров X, если несколько потоков одновременно попадут в код статической инициализации.

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

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

Гарантии безопасности потоков означает, что может быть довольно много скрытого кода, чтобы переменная вела себя так, как вы ожидаете, особенно с учетом того, что компилятор не имеет возможности заранее узнать, сколько потоков будет существуют в вашей программе, и сколько из них коснется локальной переменной потока.

Дейл Уилсон
источник
@Etherealone: ​​Интересно. Какая конкретная информация? Можете дать ссылку?
Дейл Уилсон
1
stackoverflow.com/a/8102145/1576556 . В статье Wikipedia C ++ 11 это упоминается, если я правильно помню.
Etherealone
1
Однако сначала инициализируются статические объекты, а затем назначается копия. Поэтому мне также немного неясно, включает ли поточно-безопасная инициализация полное выражение. Вероятно, это так, потому что в противном случае это не считалось бы поточно-ориентированным.
Etherealone