Чи cout синхронізовано / безпечно для потоків?


112

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

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

Чи залежить цей постачальник? Що робить gcc?


Важливо : Будь ласка, вкажіть якісь посилання на вашу відповідь, якщо ви скажете "так", оскільки мені потрібні певні докази цього.

Моя стурбованість також не стосується базових системних викликів, це добре, але потоки додають шар буферизації зверху.


2
Це залежить від постачальника. C ++ (до C ++ 0x) не має поняття про декілька потоків.
Свен

2
Що з c ++ 0x? Він визначає модель пам’яті і що це за потік, тож, можливо, ці речі пропускали на виході?
rubenvb

2
Чи є якісь постачальники, які роблять це безпечним для потоків?
edA-qa mort-ora-y

Хтось має посилання на останній запропонований стандарт C ++ 2011?
edA-qa mort-ora-y

4
У певному сенсі саме це printfсвітиться, коли повний вихід записується stdoutодним кадром; при використанні std::coutкожної ланки ланцюга вираження буде виведено окремо до stdout; між ними може бути якесь інше написання потоку, stdoutзавдяки якому кінцеве замовлення виводу псується.
legends2k

Відповіді:


106

Стандарт C ++ 03 про це нічого не говорить. Коли у вас немає гарантій щодо безпеки ниток чогось, слід ставитися до цього, як до безпечного для потоків.

Особливий інтерес тут викликає факт coutбуферизації. Навіть якщо виклики до write(або що б там не було, що досягає цього ефекту в цій конкретній реалізації) гарантуються як взаємовиключні, буфер може бути розділений різними потоками. Це швидко призведе до корупції внутрішнього стану потоку.

І навіть якщо доступ до буфера гарантовано є безпечним для потоків, що ви думаєте, що відбудеться в цьому коді?

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

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

Напевно, ви хочете, щоб кожен рядок тут діяв у взаємному виключенні. Але як це може гарантувати впровадження?

У C ++ 11 у нас є деякі гарантії. У ІССУ в §27.4.1 [iostream.objects.overview] зазначається наступне:

Одночасний доступ до синхронізованого (§27.5.3.4) стандартного об'єкта iostream відформатованого та неформатоване введення (§27.7.2.1) та виведення (§27.7.3.1) функцій або стандартного потоку С декількома потоками не повинен призводити до перегону даних (§ 1.10). [Примітка: Користувачі повинні синхронізувати одночасне використання цих об'єктів і потоків декількома потоками, якщо вони хочуть уникнути перемежованих символів. - кінцева примітка]

Отже, ви не отримаєте пошкоджених потоків, але вам все одно потрібно синхронізувати їх вручну, якщо ви не хочете, щоб вихід був сміттям.


2
Технічно це стосується C ++ 98 / C ++ 03, але я думаю, що всі це знають. Але це не дає відповіді на два цікавих питання: Що з C ++ 0x? Що насправді роблять типові реалізації ?
Немо

1
@ edA-qa mort-ora-y: Ні, ви помилилися. C ++ 11 чітко визначає, що стандартні об'єкти потоку можуть бути синхронізовані та зберігати чітко визначену поведінку, а не те, що вони за замовчуванням.
ildjarn

12
@ildjarn - Ні, @ edA-qa mort-ora-y правильний. Поки cout.sync_with_stdio()це правда, використання coutдля виведення символів з декількох потоків без додаткової синхронізації є чітко визначеним, але лише на рівні окремих байтів. Таким чином, cout << "ab";і cout << "cd"виконані в різних потоках можуть acdb, наприклад, виводити , але можуть не спричинити не визначену поведінку.
Йоханнес

4
@JohannesD: Ми згодні з цим - це синхронізовано з базовим API API. Моя думка, що це не "синхронізовано" корисно, тобто все одно потрібна ручна синхронізація, якщо вони не хочуть даних про сміття.
ildjarn

2
@ildjarn, я добре з даними про сміття, що я трохи розумію. Мене просто цікавить стан перегонів даних, який, здається, зараз зрозумілий.
edA-qa mort-ora-y

16

Це чудове питання.

По-перше, C ++ 98 / C ++ 03 не має поняття "нитка". Тож у тому світі питання безглуздо.

Що з C ++ 0x? Дивіться відповідь Мартиньо (яка, я зізнаюся, мене здивувала).

Як щодо конкретних реалізацій pre-C ++ 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.

Наскільки я можу сказати, libstdc ++ не виконує жодного блокування навколо жодної операції потоку. І я не очікував би жодного, оскільки це буде повільно.

Таким чином, з цією реалізацією, очевидно, можливо, що вихід двох потоків може пошкодити один одного (а не просто переплетення).

Чи може цей код пошкодити саму структуру даних? Відповідь залежить від можливої ​​взаємодії цих функцій; наприклад, що відбувається, якщо один потік намагається промити буфер, а інший намагається викликати xsputnабо що завгодно. Це може залежати від того, як ваш компілятор і процесор вирішать переупорядкувати завантаження та зберігання пам'яті; щоб бути впевненим, знадобиться ретельний аналіз. Це також залежить від того, що робить ваш процесор, якщо два потоки намагаються одночасно змінювати одне і те ж місце.

Іншими словами, навіть якщо у вашому поточному середовищі це нормально працює, воно може зламатися, коли ви оновлюєте будь-який час виконання, компілятор або процесор.

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

Як слабку альтернативу, ви можете встановити cout на unbuffered. Ймовірно (хоча і не гарантовано), що пропустить всю логіку, пов’язану з буфером, і виклик writeбезпосередньо. Хоча це може бути надмірно повільним.


1
Хороша відповідь, але подивіться на відповідь Мартинго, яка показує, що C ++ 11 визначає синхронізацію для cout.
edA-qa mort-ora-y

7

Стандарт C ++ не визначає, чи записування в потоки є безпечним для потоків, але зазвичай це не так.

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

а також: Чи стандартні потоки виводу в безпеці для потоків C ++ (cout, cerr, clog)?

ОНОВЛЕННЯ

Перегляньте відповідь @Martinho Fernandes, щоб дізнатися про те, що говорить про це новий стандарт C ++ 11.


3
Я думаю, оскільки C ++ 11 зараз стандарт, ця відповідь зараз неправильна.
edA-qa mort-ora-y

6

Як згадують інші відповіді, це, безумовно, специфічно для продавця, оскільки стандарт C ++ не згадує різьблення (це змінюється в C ++ 0x).

GCC не дає багато обіцянок щодо безпеки потоку та вводу-виводу. Але документація на те, що він обіцяє, тут:

ключовий матеріал, мабуть:

Тип __basic_file - це просто сукупність невеликих обгортків навколо шару C stdio (ще раз дивіться посилання в розділі Структура). Ми не робимо замикань на себе, а просто проходимо через дзвінки, щоб поспілкуватися, переписати тощо.

Отже, на 3.0, на питання "чи багатопотокова безпека для вводу / виводу", слід відповісти, "чи безпечна нитка бібліотеки С вашої платформи для вводу / виводу?" Деякі за замовчуванням, деякі ні; багато пропонують кілька реалізацій бібліотеки С із різними компромісами безпеки та ефективності потоку. Ви, програміст, завжди зобов’язані дбати про декілька потоків.

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

Отже, якщо бібліотека С на вашій платформі є безпечною для потоків, то ваші операції вводу / виводу 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 одного типу, це безпечно, якщо A записується в потоці 1, а B читається в потоці 2.

...

Іострим Класи

Класи iostream дотримуються тих же правил, що й інші класи, за одним винятком. Безпечно писати на об’єкт з декількох потоків. Наприклад, нитка 1 може записати в cout одночасно з потоком 2. Однак це може призвести до того, що вихід з двох потоків буде змішаний.

Примітка. Читання з буфера потоку не вважається операцією зчитування. Це слід розглядати як операцію запису, оскільки це змінює стан класу.

Зауважте, що ця інформація є для останньої версії MSVC (зараз для VS 2010 / MSVC 10 / cl.exe16.x). Ви можете вибрати інформацію для старих версій MSVC, використовуючи спадне управління на сторінці (а інформація відрізняється від старих версій).


1
"Я не знаю, чи змінилося щось згаданий часовий проміжок 3.0". Це точно було. Протягом останніх декількох років реалізація потоків g ++ здійснювала власну буферизацію.
Немо
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.