stdout потокобезопасен в C на Linux? - PullRequest
37 голосов
/ 22 января 2009

Пишет ли запись на стандартный вывод, используя printf поточно-ориентированный в Linux? Как насчет использования низкоуровневой команды write?

Ответы [ 5 ]

51 голосов
/ 22 января 2009

Это не определено стандартом C - это зависит от вашей реализации стандартной библиотеки C. Фактически, в стандарте C даже не упоминаются потоки, поскольку некоторые системы (например, встроенные системы) не имеют многопоточности.

В реализации GNU (glibc) большинство функций более высокого уровня в stdio, которые имеют дело с FILE* объектами, являются поточно-ориентированными. Те, которые обычно не имеют unlocked в своих именах (например, getc_unlocked(3)). Однако безопасность потоков находится на уровне вызовов для каждой функции: например, если вы делаете несколько вызовов на printf(3), то каждый из этих вызовов гарантированно выводится атомарно, но другие потоки могут распечатывать вещи между вашими вызовами на * 1008. *. Если вы хотите, чтобы последовательность вызовов ввода-вывода выводилась атомарно, вы можете окружить их парой вызовов flockfile(3)/funlockfile(3), чтобы заблокировать дескриптор FILE. Обратите внимание, что эти функции являются реентерабельными, поэтому вы можете безопасно вызывать printf() между ними, и это не приведет к тупику, даже если сама printf() сама вызовет flockfile().

Низкоуровневые вызовы ввода / вывода, такие как write(2), должны быть поточно-ориентированными, но я не уверен на 100% - write() делает системный вызов в ядре для выполнения ввода / вывода. Как именно это происходит, зависит от того, какое ядро ​​вы используете. Это может быть инструкция sysenter или инструкция int (прерывание) в старых системах. Оказавшись внутри ядра, это зависит от ядра, чтобы убедиться, что ввод / вывод является потокобезопасным. В тесте, который я только что провел с Darwin Kernel версии 8.11.1, write(2) представляется поточно-ориентированным.

22 голосов
/ 26 июля 2010

Назовите ли вы это "потокобезопасным", зависит от вашего определения поточно-безопасного. POSIX требует stdio функций для использования блокировки, поэтому ваша программа не будет аварийно завершать работу, повреждать состояния объекта FILE и т. Д., Если вы используете printf одновременно из нескольких потоков. Однако все операции stdio формально определены в терминах повторных вызовов к fgetc и fputc, поэтому не гарантируется атомарность в большем масштабе. То есть, если потоки 1 и 2 пытаются печатать "Hello\n" и "Goodbye\n" одновременно, нет гарантии, что выходные данные будут либо "Hello\nGoodbye\n", либо "Goodbye\nHello\n". С таким же успехом это может быть "HGelolodboy\ne\n". На практике большинство реализаций получит одну блокировку для всего вызова записи более высокого уровня просто потому, что это более эффективно, но ваша программа не должна этого допускать. Могут быть угловые случаи, когда это не сделано; например, реализация может, вероятно, полностью исключить блокировку небуферизованных потоков.

Редактировать: Приведенный выше текст об атомарности неверен. POSIX гарантирует, что все операции stdio являются атомарными, но эта гарантия скрыта в документации для flockfile: http://pubs.opengroup.org/onlinepubs/9699919799/functions/flockfile.html

Все функции, которые ссылаются на (FILE *) объекты, должны вести себя так, как будто они используют flockfile () и funlockfile () для получения прав собственности на эти (FILE *) объекты.

Вы можете использовать функции flockfile, ftrylockfile и funlockfile самостоятельно для достижения атомарной записи, превышающей единичный вызов функции.

13 голосов
/ 22 января 2009

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

5 голосов
/ 14 декабря 2016

C получил новый стандарт, так как этот вопрос был задан (и последний ответ).

C11 теперь поставляется с поддержкой многопоточности и обращается к многопоточному поведению потоков:

§7.21.2 Потоки

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

*8 Все функции, которые читают, пишут, позиционируют или запрашивают позицию потока, блокируют поток перед доступом к нему. Они снимают блокировку, связанную с потоком, когда доступ завершен.

Таким образом, реализация с потоками C11 должна гарантировать, что использование printf является потокобезопасным.

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

Я склоняюсь к тому, чтобы это было гарантировано. Стандарт говорит о , ограничивающем чередование, поскольку некоторое чередование, которое не меняет результат, все же допускается; например, fwrite некоторые байты, fseek еще немного назад и fwrite до исходного смещения, так что оба fwrite смещены вплотную. Реализация свободна переупорядочить эти 2 fwrite с и объединить их в одну запись.


1 : См. Зачеркнутый текст в ответе R .. для примера.

4 голосов
/ 22 января 2009

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...