Является ли запись в стандартный вывод с использованием printf потокобезопасной в Linux? А как насчет использования команды нижнего уровня write?





Это потокобезопасный; printf должен быть реентерабельным, и вы не вызовете каких-либо странностей или искажений в своей программе.
Вы не можете гарантировать, что ваш вывод из одного потока не начнется на полпути вывода из другого потока. Если вам это небезразлично, вам необходимо разработать собственный заблокированный выходной код для предотвращения множественного доступа.
Я не знаю, что делают другие, но библиотека GNU C по умолчанию является поточно-ориентированной, поэтому нет, она не будет использовать тот же буфер.
Я не думаю, что printf повторно входим, см. stackoverflow.com/questions/3941271/…
Они оба являются потокобезопасными до такой степени, что ваше приложение не выйдет из строя, если несколько потоков вызовут их на одном и том же файловом дескрипторе. Однако без какой-либо блокировки на уровне приложения все, что написано, может чередоваться.
Это не определено стандартом C - это зависит от вашей реализации стандартной библиотеки C. Фактически, стандарт C даже не упоминает потоки, поскольку некоторые системы (например, встроенные системы) не имеют многопоточности.
В реализации GNU (glibc) большинство высокоуровневых функций в stdio, которые работают с объектами FILE*, являются потокобезопасными. Те, в именах которых обычно нет unlocked (например, getc_unlocked(3)). Однако безопасность потоков находится на уровне вызова каждой функции: например, если вы делаете несколько вызовов printf(3), каждый из этих вызовов гарантированно выводится атомарно, но другие потоки могут распечатывать данные между вашими вызовами printf(). Если вы хотите гарантировать, что последовательность вызовов ввода-вывода выводится атомарно, вы можете окружить их парой вызовов flockfile(3)/funlockfile(3), чтобы заблокировать дескриптор FILE. Обратите внимание, что эти функции являются реентерабельными, поэтому вы можете безопасно вызывать printf() между ними, и это не приведет к тупиковой ситуации, даже если printf() сам выполняет вызов flockfile().
Вызовы низкоуровневого ввода-вывода, такие как write(2), должны быть потокобезопасными, но я не уверен в этом на 100% - write() выполняет системный вызов ядра для выполнения ввода-вывода. Как именно это происходит, зависит от того, какое ядро вы используете. Это может быть инструкция sysenter или инструкция int (прерывание) в старых системах. Оказавшись внутри ядра, ядро должно убедиться, что ввод-вывод является потокобезопасным. В тесте, который я только что провел с ядром Дарвина версии 8.11.1, write(2) оказался потокобезопасным.
Этот ответ игнорирует то, что вопрос был помечен как unix / linux. POSIX требует, чтобы stdio был потокобезопасным, что весьма прискорбно, поскольку оно снижает производительность и поскольку нет практического способа работать с одним и тем же ФАЙЛОМ из нескольких потоков (данные будут выходить безнадежно чередующимися; атомарность только на уровне символов).
Иногда вполне нормально, если вывод чередуется, например во время регистрации через printf из нескольких потоков.
@couling Я думаю, он имеет в виду, что потокобезопасность бесполезна, потому что все равно все чередуется - если вы все равно не используете явный файл блокировки f [un].
Назовете ли вы это «потокобезопасным», зависит от вашего определения потоковой безопасности. 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
All functions that reference ( FILE *) objects shall behave as if they use flockfile() and funlockfile() internally to obtain ownership of these ( FILE *) objects.
Вы можете использовать функции flockfile, ftrylockfile и funlockfile самостоятельно, чтобы добиться атомарной записи большего размера, чем один вызов функции.
C получил новый стандарт с тех пор, как был задан этот вопрос (и был дан последний ответ).
C11 теперь поддерживает многопоточность и учитывает многопоточное поведение потоков:
§7.21.2 Streams
¶7 Each stream has an associated lock that is used to prevent data races when multiple threads of execution access a stream, and to restrict the interleaving of stream operations performed by multiple threads. Only one thread may hold this lock at a time. The lock is reentrant: a single thread may hold the lock multiple times at a given time.
¶8 All functions that read, write, position, or query the position of a stream lock the stream before accessing it. They release the lock associated with the stream when the access is complete.
Таким образом, реализация с потоками C11 должна гарантировать, что использование printf является потокобезопасным.
Гарантируется ли атомарность (как в случае отсутствия чередования 1), на первый взгляд мне было не так ясно, потому что в стандарте говорилось о чередовании ограничение, в отличие от предотвращение, которое он предписывал для гонок данных.
Я склоняюсь к тому, чтобы это было гарантировано. В стандарте говорится о чередовании ограничение, поскольку некоторое чередование, которое не меняет результат, все еще может происходить; напримерfwrite несколько байтов, fseek еще несколько байтов и fwrite до исходного смещения, так что оба fwrite находятся один за другим. Реализация может переупорядочить эти 2 fwrite и объединить их в одну запись.
1: См. Зачеркнутый текст в R .. ответ в качестве примера.
Все вызовы printf, вероятно, будут использовать один и тот же буфер для построения строки. Многие реализации также используют буфер между scanf и printf, что может вызвать некоторые странные ошибки, связанные с отладкой.