Подтвердить что ты не робот

Стандартные контейнеры в качестве локальных переменных в многопоточном приложении

Я знаю, что контейнеры из стандартной библиотеки не являются потокобезопасными. При этом я думал, что контейнер, например тип std::list, не может быть доступен более чем одним потоком одновременно (некоторые из них могут модифицировать контейнер). Но теперь кажется, что есть нечто большее, чем кажется на первый взгляд; что-то более тонкое, что-то не столь очевидное, ну, по крайней мере, для меня.

Например, рассмотрим эту функцию, которая принимает первый аргумент по значению:

void log(std::string msg, severity s, /*...*/) 
{
   return; //no code!
}

Является ли это потокобезопасным?

Поначалу кажется, что он является потокобезопасным, поскольку тело функции не обращается к общим изменяемым ресурсам, поэтому поточно-безопасным. С другой стороны, мне приходит в голову, что при вызове такой функции будет создан объект типа std::string, который является первым аргументом, и я думаю, что построение этого объекта не является потокобезопасным, поскольку оно внутренне использует std::allocator, который, по моему мнению, не является потокобезопасным. Следовательно, вызов такой функции также не является потокобезопасным. Но если это правильно, то как насчет этого:

void f()
{
   std::string msg = "message"; //is it thread-safe? it doesn't seem so!
}

Я иду вправо? Можем ли мы использовать std::string (или любой контейнер, который использует std::allocator внутри) в многопоточной программе?

Я говорю о контейнерах как о локальных переменных, а не об общих объектах.

Я искал google и нашел много подобных сомнений, без конкретного ответа. Я сталкиваюсь с аналогичной проблемой, как и его:

Пожалуйста, рассмотрите С++ 03 и С++ 11, оба.

4b9b3361

Ответ 1

В С++ 11 std::allocator является потокобезопасным. Из его определения:

20.6.9.1/6: Примечание: память получается путем вызова ::operator new(std::size_t)

и из определения ::operator new:

18.6.1.4: версии библиотеки operator new и operator delete, версии замены пользователя глобальных operator new и operator delete и стандартные библиотечные функции C calloc, malloc, realloc, и freeне вводить расы данных (1.10) в результате одновременных вызовов из разных потоков.

С++ 03 не имел понятия потоков, поэтому любая безопасность потоков была специфичной для реализации; вам нужно будет обратиться к своей документации по внедрению, чтобы узнать, какие гарантии он предлагает, если таковые имеются. Поскольку вы используете реализацию Microsoft, эта страница говорит, что безопасно записывать в несколько объектов контейнера одного класса из многих потоков, что подразумевает что std::allocator является потокобезопасным.

Ответ 2

В С++ 11 это будет адресовано для распределителя по умолчанию в:

20.6.9.1 члены распределителя [allocator.members]

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

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

Конечно, для более ранних версий стандарта ничего не говорится об этом, так как они не говорили о многопоточности. Если бы реализация поддерживала многопоточность (как многие, так и большинство), она отвечала бы за решение этих проблем. Подобно тому, как реализация обеспечивает потокобезопасную malloc() (и другие библиотечные функции) для C и С++, хотя стандарты до недавнего времени ничего не говорили об этом.

Ответ 3

Как вы, возможно, уже поняли, не будет легкого ответа "да" или "нет". Однако я думаю, что это может помочь:

http://www.cs.huji.ac.il/~etsman/Docs/gcc-3.4-base/libstdc++/html/faq/index.html#5_6

Я цитирую дословно:

5.6 Является ли libstdС++ - v3 потокобезопасным?

libstdС++ - v3 стремится быть потокобезопасным, когда все следующие условия выполняются:

Система libc сама по себе является потокобезопасной, gcc -v сообщает модель потока, отличную от "single", [pre-3.3 only] существует неэквивалентная реализация atomicity.h существует для рассматриваемой архитектуры.

Ответ 4

Когда во время вызова log копируется std::string, распределитель может быть потокобезопасным (обязательно в С++ 11), но сама копия отсутствует. Так что, если есть другой поток, изменяющий исходную строку во время копирования, это не является безопасным для потоков.

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


OTOH,...

std::string msg = "message";

... является потокобезопасным, если ваш распределитель является потокобезопасным.