Является ли запись на stdout с помощью printf
потокобезопасной в Linux? Как насчет использования команды write
нижнего уровня?
Stdout поточно-безопасный в C на Linux?
Ответ 1
Он не указан стандартом 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)
кажется потокобезопасным.
Ответ 2
Будете ли вы называть его "потокобезопасным", зависит от вашего определения потокобезопасности. 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
самостоятельно, чтобы выполнить атомарную запись более крупного, чем однострочный.
Ответ 3
Они обе потокобезопасны до такой степени, что ваше приложение не будет разбиваться, если несколько потоков вызовут их в одном дескрипторе файла. Однако, без какой-либо блокировки на уровне приложения, все, что написано, может быть перемежено.
Ответ 4
Это поточно-безопасный; printf должен быть реентерабельным, и вы не будете вызывать каких-либо странностей или коррупции в вашей программе.
Вы не можете гарантировать, что ваш вывод из одного потока не будет запускаться наполовину через выход из другого потока. Если вам небезразлично, вам нужно разработать собственный заблокированный выходной код для предотвращения множественного доступа.
Ответ 5
C получил новый стандарт, так как этот вопрос был задан (и последний ответ).
Теперь C11 поддерживает многопоточность и поддерживает многопоточное поведение потоков:
§7.21.2 Потоки
¶7 Каждый поток имеет связанную блокировку, которая используется для предотвращения расы данных, когда несколько потоков выполнения доступа к потоку и ограничивают перемежение операций потока, выполняемых несколькими потоками. Только один поток может удерживать эту блокировку одновременно. Блокировка реентерабельна: один поток может удерживать блокировку несколько раз за определенное время.
¶8. Все функции, которые читают, записывают, позиционируют или запрашивают позицию потока, блокируют поток до его доступа. Они освобождают блокировку, связанную с потоком, когда доступ завершен.
Итак, реализация с потоками C11 должна гарантировать, что использование printf
является потокобезопасным.
Является ли атомарность (как при отсутствии перемежения 1), было не так ясно для меня на первый взгляд, потому что в стандарте говорилось об ограничении чередования, а не в предотвращении, которое оно поручило для гонок данных.
Я склоняюсь к тому, чтобы быть уверенным. Стандарт говорит об ограничении чередования, поскольку по-прежнему допускается некоторое перемежение, которое не изменяет результат; например fwrite
несколько байтов, fseek
верните еще немного и fwrite
до исходного смещения, так что оба fwrite
будут обратными. Реализация может изменить эти 2 fwrite
и объединить их в одну запись.
1: см. текст проталкивания в R.. answer для примера.