Stdout потокобезопасный в C в Linux?

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

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
44
0
43 078
5
Перейти к ответу Данный вопрос помечен как решенный

Ответы 5

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

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

Все вызовы printf, вероятно, будут использовать один и тот же буфер для построения строки. Многие реализации также используют буфер между scanf и printf, что может вызвать некоторые странные ошибки, связанные с отладкой.

Martin Beckett 22.01.2009 07:04

Я не знаю, что делают другие, но библиотека GNU C по умолчанию является поточно-ориентированной, поэтому нет, она не будет использовать тот же буфер.

Adam Hawes 23.01.2009 00:54

Я не думаю, что printf повторно входим, см. stackoverflow.com/questions/3941271/…

Yu Hao 03.06.2013 10:05

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

Ответ принят как подходящий

Это не определено стандартом 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 был потокобезопасным, что весьма прискорбно, поскольку оно снижает производительность и поскольку нет практического способа работать с одним и тем же ФАЙЛОМ из нескольких потоков (данные будут выходить безнадежно чередующимися; атомарность только на уровне символов).

R.. GitHub STOP HELPING ICE 26.07.2010 17:46

Иногда вполне нормально, если вывод чередуется, например во время регистрации через printf из нескольких потоков.

nob 19.02.2013 21:13

@couling Я думаю, он имеет в виду, что потокобезопасность бесполезна, потому что все равно все чередуется - если вы все равно не используете явный файл блокировки f [un].

Adrian Ratnapala 01.12.2013 10:44

Назовете ли вы это «потокобезопасным», зависит от вашего определения потоковой безопасности. 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 .. ответ в качестве примера.

Другие вопросы по теме