Является ли cout синхронизированным/потокобезопасным?

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

То есть, если несколько потоков записывают в cout, они могут повредить объект cout? Я понимаю, что даже если вы синхронизированы, вы все равно получите случайный чередованный вывод, но это гарантированное чередование. То есть безопасно ли использовать cout из нескольких потоков?

Является ли этот поставщик зависимым? Что делает gcc?


Важно: Пожалуйста, предоставьте какую-то ссылку на ваш ответ, если вы скажете "да", так как мне нужно какое-то подтверждение этого.

Моя забота также не о базовых системных вызовах, это прекрасно, но потоки добавляют слой буферизации сверху.

Ответ 1

В стандарте С++ 03 ничего не говорится об этом. Если у вас нет никаких гарантий относительно безопасности потоков для чего-то, вы должны относиться к нему как к потокобезопасному.

Особый интерес представляет тот факт, что cout буферизуется. Даже если вызовы на write (или что бы то ни было, что выполняет этот эффект в этой конкретной реализации) гарантированно будут взаимоисключающими, буфер может быть разделен различными потоками. Это быстро приведет к повреждению внутреннего состояния потока.

И даже если доступ к буфере гарантированно будет потокобезопасным, что, по вашему мнению, произойдет в этом коде?

// in one thread
cout << "The operation took " << result << " seconds.";

// in another thread
cout << "Hello world! Hello " << name << "!";

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

В С++ 11 у нас есть некоторые гарантии. FDIS говорит следующее в §27.4.1 [iostream.objects.overview]:

Параллельный доступ к синхронизированным (§27.5.3.4) стандартным объектам iostream форматированным и неформатированным входам (§27.7.2.1) и выводам (§27.7.3.1) или стандартного потока C несколькими потоками не должны в гонке данных (§1.10). [Примечание. Пользователи должны синхронизировать одновременное использование этих объектов и потоков несколько потоков, если они хотят избежать чередующихся символов. - конечная нота]

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

Ответ 2

Это отличный вопрос.

Во-первых, С++ 98/С++ 03 не имеет понятия "поток". Поэтому в этом мире вопрос не имеет смысла.

Как насчет С++ 0x? См. ответ Martinho (который, я признаю, удивил меня).

Как насчет конкретных реализаций pre-С++ 0x? Ну, например, вот исходный код для basic_streambuf<...>:sputc из GCC 4.5.2 (заголовок streambuf):

 int_type
 sputc(char_type __c)
 {
   int_type __ret;
   if (__builtin_expect(this->pptr() < this->epptr(), true)) {
       *this->pptr() = __c;
        this->pbump(1);
        __ret = traits_type::to_int_type(__c);
      }
    else
        __ret = this->overflow(traits_type::to_int_type(__c));
    return __ret;
 }

Ясно, что это не блокирует. И не имеет значения xsputn. И это определенно тип streambuf, который использует cout.

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

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

Может ли этот код повредить структуру данных? Ответ зависит от возможных взаимодействий этих функций; например, что происходит, если один поток пытается сбросить буфер, а другой пытается вызвать xsputn или что-то еще. Это может зависеть от того, как ваш компилятор и процессор решат изменить порядок загрузки и хранения памяти; это будет тщательный анализ, чтобы быть уверенным. Это также зависит от того, что делает ваш процессор, если два потока пытаются одновременно изменить одно и то же местоположение.

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

Резюме: "Я бы не стал". Создайте класс ведения журнала, который делает правильную блокировку, или перейдите к С++ 0x.

В качестве слабой альтернативы вы можете настроить cout на небуферизованный. Вероятно (хотя и не гарантировано), что пропустит всю логику, связанную с буфером, и вызовет write напрямую. Хотя это может быть непомерно медленным.

Ответ 3

В стандарте С++ не указывается, является ли запись потоков потокобезопасной, но обычно это не так.

www.techrepublic.com/article/use-stl-streams-for-easy-c-plus-plus-thread-safe-logging

а также: Являются ли стандартные потоки вывода в С++ потокобезопасными (cout, cerr, clog)?

UPDATE

Пожалуйста, взгляните на ответ @Martinho Fernandes, чтобы узнать о том, что говорит об этом новый стандарт С++ 11.

Ответ 4

Как упоминают другие ответы, это определенно зависит от поставщика, поскольку стандарт С++ не упоминает о потоковом (это изменяется в С++ 0x).

GCC не делает много promises о безопасности потоков и ввода-вывода. Но документация для того, что она обещает, находится здесь:

возможно, что ключевой материал:

Тип __basic_file - это просто сбор мелких оберток вокруг слой C stdio (снова см. ссылку в разделе "Структура" ). Мы не блокируем сами, но просто переходим к звонки в fopen, fwrite и т.д.

Итак, для 3.0 вопрос "есть многопоточный сейф для ввода/вывода" должен быть ответил: "Ваша платформа C библиотека потокобезопасна для ввода-вывода?" Некоторые из них по умолчанию некоторые из них не являются; многие предлагают множественные реализации C библиотеки с различными компромиссами безопасность потоков и эффективность. Вы, программист, всегда требуется позаботьтесь о нескольких потоках.

(Например, стандарт POSIX требует, чтобы операции C stdio FILE * являются атомарными. POSIX-совместимый C библиотек (например, на Solaris и GNU/Linux) имеют внутренний мьютекс для сериализуйте операции над файлом * s. Однако вам все равно не нужно делать глупые вещи, такие как вызов fclose (fs) в одном потоке, за которым следует доступ fs в другом.)

Итак, если ваша библиотека платформы C threadsafe, то ваш ввод/вывод Fstream операции будут потокобезопасными на самый низкий уровень. Для более высокого уровня операций, таких как манипулирование данные, содержащиеся в потоке классы форматирования (например, настройка обратные вызовы внутри std:: ofstream), вам необходимо охранять такие обращения, как любой другой важный общий ресурс.

Я не знаю, изменилось ли какое-либо изменение в 3,0 таймфреймах.

Документация по безопасности потоков MSVC для iostreams можно найти здесь: http://msdn.microsoft.com/en-us/library/c9ceah3b.aspx:

Один объект является потокобезопасным для чтение из нескольких потоков. Для Например, учитывая объект A, он безопасен читать A из потока 1 и из нить 2 одновременно.

Если один объект записывается в одним потоком, тогда все читает и записывает этот объект на том же или другие потоки должны быть защищены. Для Например, если задан объект A, если поток 1 записывает в A, тогда поток 2 должен препятствовать чтению или запись в A.

Безопасно читать и писать одному экземпляр типа, даже если другой поток читает или записывает другой экземпляр того же типа. Например, данные объекты A и B of тот же тип, это безопасно, если A записывается в потоке 1, а B - читается в потоке 2.

...

классы iostream

Классы iostream следуют тем же как другие классы, с одним исключение. Безопасно писать в объект из нескольких потоков. Для Например, поток 1 может писать в cout at то же время, что и поток 2. Однако, это может привести к два потока смешиваются.

Примечание. Чтение из буфера потока не считаются операцией чтения. Его следует рассматривать как запись потому что это изменяет состояние класса.

Обратите внимание, что эта информация относится к последней версии MSVC (в настоящее время для VS 2010/MSVC 10/ cl.exe 16.x). Вы можете выбрать информацию для более старых версий MSVC, используя выпадающий элемент управления на странице (и информация отличается для более старых версий).