Атомный системный вызов. Операции ввода/вывода

Я хотел бы написать многопоточный безопасный регистратор, используя очередь без блокировки. Потоки ведения журнала будут помещать сообщения в очередь, а регистратор будет извлекать их и отправлять на вывод. Думаю как решить этот вопрос- отправка на выход. Я хотел бы избегать использования мьютексов/блокировок, насколько это возможно. Итак, предположим, что я собираюсь использовать потоки C++ для записи в файл/консоль. Мы можем предположить, что целевой системой является Linux.

Хорошо, запись в поток должна быть просто оболочкой (возможно, расширенной оболочкой) для системного вызова, предлагаемого Unix write. Из того, что я знаю, системные вызовы являются атомарными (только один процесс может выполнять системный вызов одновременно). Таким образом, заманчиво не использовать блокировки для безопасной записи в файл. Но write - это системный вызов, но он не гарантирует записи "всего вывода". Он возвращает количество байтов, которые были успешно записаны в файл.

В общем, мой вопрос: как это решить? Можно ли избежать мьютекса? (думаю, что нельзя). И, пожалуйста, отметьте мои соображения, я ошибаюсь?


person Gilgamesz    schedule 27.08.2016    source источник
comment
Я не уверен, что вполне понимаю природу проблемы. Как вы думаете, с кем вы конкурируете? Взаимное исключение предполагает наличие как минимум двух сторон, пытающихся поделиться ресурсом — кто эта вторая сторона, о которой вы беспокоитесь?   -  person Igor Tandetnik    schedule 27.08.2016
comment
Есть два потока, конкурирующих за поток.   -  person Gilgamesz    schedule 27.08.2016
comment
Ну, у вас есть эта сложная установка с очередью, явно предназначенная для того, чтобы только один поток фактически выполнял ввод-вывод. Зачем тогда вам саботировать собственный дизайн, вводя больше потоков, записывающих в один и тот же файл? Какой смысл иметь эту очередь, если вы затем сделаете запись в файл доступной для всех?   -  person Igor Tandetnik    schedule 27.08.2016
comment
@IgorTandetnik, я сказал: логгер выскочит. Я не имел в виду именно одну нить. Там может быть, например, 3 потока, которые выталкивают и пишут в файл то, что они выталкивали.   -  person Gilgamesz    schedule 27.08.2016
comment
Но почему? Казалось бы, это полностью противоречит сути упражнения. Пусть один поток выполняет всю запись в файл: проблема решена.   -  person Igor Tandetnik    schedule 27.08.2016
comment
Да, я сделал это для себя. Я знаю, что один поток и запись - это решение. Но я предпочитаю больше писать потоки для целей обучения.   -  person Gilgamesz    schedule 27.08.2016
comment
Позвольте мне получить это прямо. Вы начинаете с проблемы: несколько потоков должны записывать в один файл журнала. Чтобы решить эту проблему, вы вводите очередь — все потоки помещают в нее свои сообщения, а затем что-то их выталкивает и выписывает. Затем вы говорите - я хочу, чтобы несколько потоков выполняли это всплывающее и записывающее. После чего вы вернулись к тому, с чего начали — у вас есть несколько потоков, которые хотят записать в один и тот же файл. Вы ни на шаг не приблизились к своей цели. Если бы существовал какой-то волшебный способ координировать этот новый набор потоков, вы могли бы просто использовать тот же способ для координации исходных потоков.   -  person Igor Tandetnik    schedule 27.08.2016


Ответы (1)


Игорь прав: пусть один поток делает все, что пишет лог. Имейте в виду, что ядро ​​должно выполнять блокировку для синхронизации доступа к дескриптору открытого файла (который отслеживает положение файла), поэтому, выполняя запись из нескольких ядер, вы вызываете конкуренцию внутри ядра. Хуже того, вы выполняете системные вызовы из нескольких ядер, а это означает, что доступ к коду/данным ядра загрязнит ваши кеши на нескольких ядрах.

Дополнительную информацию о влиянии создания системные вызовы производительности кода пользовательского пространства после завершения системного вызова. (И про промахи кеша данных/инструкций внутри ядра при нечастых системных вызовах). Определенно имеет смысл иметь один поток, выполняющий все системные вызовы, по крайней мере, все системные вызовы записи, чтобы изолировать эту часть вашего процесса от одного ядра. А также конфликт блокировок внутри ядра.

В этом документе FlexSC рассказывается об идее группирования системных вызовов для уменьшения переходов пользователь->ядро->пользователь, но они также измеряют накладные расходы для обычного метода синхронного системного вызова. Более важным является обсуждение загрязнения кеша системными вызовами.


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

Не гарантируется, что большая запись будет завершена без перерыва, но запись малого и среднего размера должна (почти?) всегда копировать весь свой буфер в большинстве ОС. Особенно, если вы пишете в файл, а не в канал. IDK, как Linux write() ведет себя, когда он вытеснен, но я ожидаю, что он обычно возобновляет запись, а не возвращает, не записав все запрошенные байты. Частичная запись может быть более вероятной, когда прерывается сигналом.

Гарантируется, что байты от двух системных вызовов write() не будут перемешаны; все байты из одного будут до или после байтов из другого. Однако вы правы в том, что частичная запись является потенциальной проблемой. Я забыл, возобновит ли оболочка системного вызова glibc вызов для вас на EINTR. Хотя в этом случае это означает, что на самом деле не было записано ни одного байта, иначе он вернул бы успех с подсчетом байтов.

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

person Peter Cordes    schedule 27.08.2016
comment
спасибо :) И когда вы тестируете это, убедитесь, что вы делаете это с какой-то реальной работой, происходящей в вашем пользовательском пространстве, а не просто с циклом, который вызывает только запись.) Да, я встречал это :) - lock-contion : ) - person Gilgamesz; 28.08.2016