Подтвердить что ты не робот

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

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

4b9b3361

Ответ 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 для примера.