буфер ostream защищен мьютексом?

имеется такой надуманный участок кода, поток вывода намеренно не защищен мьютексом

#include <iostream>
#include <string_view>
#include <thread>

void print(std::string_view message)
{
  for (int i = 0;i < 10000;++i) {
    std::cout << message;
  }
}

int main()
{
  std::thread t1(print, "hello\n");
  std::thread t2(print, "aloha\n");
  t1.join();
  t2.join();

  return 0;
}

вывод получается какого-то такого вида рандомный

...
hello
hello
aloha
hello
aloha
...

но почему слова выводятся целиком, не перемешиваясь символами например так haelohallo, это значит что буфер потока вывода защищен мьютексом и не позволяет в него записывать нескольким потокам или устроено каким-то другим хитрым образом?


Ответы (1 шт):

Автор решения: Fat-Zer

Стандарт ([iostreams.threadsafety]) говорит нам:

Concurrent access to a stream object, stream buffer object, or C Library stream by multiple threads may result in a data race unless otherwise specified.
[Note: Data races result in undefined behavior. — end note]

Но для стандартных потоков явно оговаривает ([iostream.objects.overview]):

Concurrent access to a synchronized standard iostream object's formatted and unformatted input and output functions or a standard C stream by multiple threads does not result in a data race.
[Note: Users must still synchronize concurrent use of these objects and streams by multiple threads if they wish to avoid interleaved characters. — end note]

Вольный перевод:

Параллельный доступ к объекту потока, буферу потока или потокам из библиотеки Си множеством нитей может вызвать гонку данных, если иное не указано.
[Замечание: Гонка данных вызывает неопределённое поведение.]

Параллельный доступ к функциям объекта стандартного потока ввода/вывода (как форматированного, так и не форматированного) или стандартного потока Си множеством нитей не приводит к гонке данных.
[Замечание: Пользователям всё ещё необходимо синхронизировать одновременное использование этих объектов, если они хотят избежать чередование символов.]

Таким образом, синхронизация обеспечивается только для cin/cout/cerr. Для них гарантируется, что символы при вводе/выводе не будут пропадать/дублироваться, но, строго говоря, это не гарантирует, что даже при выводе целых строк они не будут перемежаться, хотя по факту это и оказывается так на большинстве реализаций. Также стоит учесть, что конструкции вида:

std::cout << "Message: " << message;

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

Для всех прочих потоков (файловых/строковых итп) всё это являтся неопределённым поведением и, как обычно, может вызвать всё что угодно: от корректной работы до исчезновения/дублирования данных в выводе/ порчи объекта потока, спонтанных самовозгораний и вылета демонов из носа.

буфер ostream защищен мьютексом?

Строго говоря, использование именно мьютекса не обязательно, но на большинстве реализаций для стандартных потоков — да, так и есть. Т.к. обычно плюсовые стандартные потоки — это обёртки для Сишных (фактически стандарт подразумевает такую реализацию), то конкретика зависит от реализации libc. Так мьютексы или аналогичные им структуры используются в glibc, uclibc, newlib и BSD libc (само собой, если сама libc была собрана с поддержкой многопоточности.).

Для файловых и строковых потоков ни какая синхронизация не обязательна. Так в гнутой libstdс++ её нет, хотя во всех перечисленных libc Сишные файловые потоки по умолчанию синхронизируются аналогично стандартным.


Как уже отметили в комментариях, в С++20 для того, чтобы адекватно писать в поток параллельно добавят osyncstream.


Спасибо @dIm0n за указание на ошибки в изначальном ответе.
→ Ссылка