c++ - мьютекс или flock fcntl.h для блокировки только операции записи

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

Я изучил flock в fcntl.h, и он говорит, что flock может выполнять гранулированную блокировку вместе с межпроцессным взаимодействием, что в моей ситуации не требуется.

char* file = "newfile.txt";
int fd;
struct flock lock;

printf("opening %s\n", file);
fd = open(file, O_APPEND);
if (fd >= 0) {
    memset(&lock, 0, sizeof (lock));
    lock.l_type = F_WRLCK;
    fcntl(fd, F_SETLKW, &lock);
    //do my thing here
    lock.l_type = F_UNLCK;
    fcntl(fd, F_SETLKW, &lock);
    close(fd);
}

Будут ли накладные расходы, поскольку он может выполнять гранулированную блокировку и блокировку между процессами? Что происходит, когда программа аварийно завершает работу при блокировке?

В настоящее время я предпочитаю мьютекс,

static std::mutex fileMutex;
fileMutex.lock();
//do my thing here    
fileMutex.unlock();

Можно ли использовать мьютексный подход, поскольку синхронизация (или блокировка) необходима только внутри процесса (только многопоточность),

или можно реализовать код с flock в fcntl.h?


person fury.slay    schedule 13.02.2017    source источник
comment
открыть в режиме O_APPEND!   -  person Jean-Baptiste Yunès    schedule 13.02.2017


Ответы (2)


Вам может не понадобиться какая-либо блокировка.

Сделайте вызов open() с установленным флагом O_APPEND, как упоминает @Jean-BaptisteYunès в комментариях.

Затем запишите свои данные с помощью одного вызова write(). POSIX гарантирует, что если файл будет открыт в режиме добавления, отдельные write() операции будут атомарными. Согласно стандарту POSIX:

Если флаг O_APPEND флагов состояния файла установлен, смещение файла должно быть установлено в конец файла перед каждой записью, и никакая промежуточная операция модификации файла не должна выполняться между изменением смещения файла и операцией записи.< /strong> [выделено мной]

Ваша единственная проблема заключается в том, как обрабатывать частичное write(), когда одна операция write() не записывает все запрошенные данные. Стандарт требует, чтобы каждая операция write() была атомарной — это не гарантирует, что запрос на запись 34 МБ приведет к записи всех 34 МБ. По моему опыту, частичные write() обращения к реальным файлам просто не происходят до тех пор, пока write() не вызовет запрос на перемещение большого количества байтов. 1 МБ — и я выполнил установку SAN и сравнительный анализ для нескольких крупных организаций.

Таким образом, если вы ограничите свои write() вызовы до PIPE_BUF или менее байтов (в Linux), вы почти наверняка сможете избежать всех блокировок и позволить внутренней блокировке внутри ядра решить вашу проблему.

Дополнительные сведения см. в следующих разделах:

Является ли добавление файла атомарным в UNIX?

Не забудьте также прочитать вопросы, связанные оттуда.

person Andrew Henle    schedule 13.02.2017
comment
что, если запись превышает PIPE_BUF, размер может легко превысить. Теперь я могу думать только о мьютексе с записью в потоковом режиме. - person fury.slay; 14.02.2017
comment
@fury.slay Поскольку вы работаете в Linux, если вы контролируете всю свою платформу, включая файловую систему, в которую вы будете писать, вам придется все протестировать и выяснить, каков фактический предел. См. stackoverflow.com/questions/10650861 Если бы вы работали на Solaris или AIX, вы почти наверняка были бы в безопасности до более чем 1 МБ. за операцию записи (обратите внимание на ссылку, что в ZFS - от Solaris - каждый write() был атомарным...) В более поздних версиях Linux вы можете быть. - person Andrew Henle; 14.02.2017
comment
Я могу протестировать на своей системе, но приложение предназначено для запуска на разных типах ОС Linux пользователей, поэтому у меня нет никаких гарантий безопасности. Поправьте меня, если я ошибаюсь. - person fury.slay; 15.02.2017

Сначала вам нужно уточнить несколько потоков по сравнению с несколькими процессами:

Несколько потоков: я бы предложил использовать мьютекс. Вы также можете сделать это более эффективным, добавляя сообщения журнала в конец буфера памяти, и вы защищаете доступ к этому буферу только с помощью мьютекса. Затем у вас может быть другой поток или какая-либо обычная функция обслуживания, которая сбрасывает буфер в файл, не блокируя другие потоки во время выполнения ввода-вывода и не блокируя файл. Тогда доступ к файлу не будет защищен ни мьютексом, ни блокировкой файла, потому что каждый из них будет доступен только одному потоку.

(Поскольку вы указываете, что межпроцессное взаимодействие отсутствует, я бы предложил пойти по этому пути.)

Несколько процессов. Вы должны использовать какой-то механизм блокировки, который виден всем процессам, например. файловые блокировки, которые вы предлагаете.

Оба. Используйте оба механизма. Старайтесь заблокировать файл только на абсолютно минимальное время.

Боковой узел:

Первое правило многопоточного программирования — не «использовать мьютекс», а «стараться не обращаться к данным несколькими потоками» или даже «стараться не использовать несколько потоков, кроме случаев, когда это абсолютно необходимо по соображениям производительности» (например, вы можете всегда обходитесь без потоков для асинхронных операций).

person Johannes Overmann    schedule 13.02.2017
comment
я упомянул, что отсутствует межпроцессное взаимодействие (отсутствие нескольких процессов) - person fury.slay; 13.02.2017
comment
Итак, вы предполагаете, что в случае многопоточного мьютекса это намного лучше, чем стадо, и что нет необходимости идти на стаю, не так ли? - person fury.slay; 13.02.2017
comment
@fury.slay: Да, определенно. Вы можете поместить мьютекс вокруг своих файловых операций. Это было бы функционально достаточно и правильно, но медленно. Поэтому я бы предложил каждый доступ к файлу только из одного конкретного потока (поэтому вам не нужен мьютекс для файлового ввода-вывода), а затем вы защищаете буфер общей памяти (очередь строк или байтов или что-то еще), используя мьютекс. - person Johannes Overmann; 13.02.2017