Я пытаюсь добавить (написать приложение) в файл из разных потоков (аналогично ведению журнала), поэтому межпроцессная блокировка не требуется.
Я изучил 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?
Вам может не понадобиться любой замок.
Сделай свой open()
позвонить с O_APPEND
флаг установлен, как @ Jean-BaptisteYunès упоминает в комментариях.
Затем запишите свои данные, используя не замужем write()
вызов. POSIX гарантирует, что если файл открывается в режиме добавления, отдельный write()
операции будут атомарными. По стандарту POSIX:
Если установлен флаг O_APPEND флагов состояния файла, то смещение файла
должен быть установлен в конец файла перед каждой записью и нет
промежуточная операция изменения файла должна происходить между изменениями
смещение файла и операция записи. [акцент мой]
Ваша единственная проблема заключается в том, как справиться с частичным write()
— где один write()
операция не записывает все запрошенные данные. Стандарт требует каждого write()
операция должна быть атомарной — это не гарантирует, что запрос на запись 34 МБ приведет к записи всех 34 МБ. По моему опыту, частичное write()
вызовы реальных файлов просто не происходят, пока write()
вызывает запрос на перемещение большого количества байтов — я никогда не наблюдал частичного write()
результат любой операции ввода-вывода на файл менее 1 МБ — и я выполнил установку и тестирование SAN для довольно большого количества организаций.
Так что, если вы ограничиваете write()
звонки под PIPE_BUF
или меньше байтов (в Linux), вы почти наверняка можете избежать всех блокировок и позволить внутренней блокировке внутри ядра решить вашу проблему.
Для получения дополнительной информации см. Следующее:
Является ли файл append атомарным в UNIX?
Обязательно прочитайте вопросы, связанные оттуда, тоже.
Во-первых, вам нужно уточнить несколько потоков против нескольких процессов:
Несколько потоков: Я бы предложил использовать мьютекс. Вы также можете сделать это более эффективным, добавив сообщения журнала в конец буфера памяти, и вы только защищаете доступ к этому буферу, используя мьютекс. После этого вы можете создать другой поток или какую-либо обычную функцию обслуживания, чтобы сбросить буфер в файл, не блокируя другие потоки во время ввода-вывода и не блокируя файл. В этом случае доступ к файлу не будет защищен ни мьютексом, ни блокировкой файла, поскольку каждый будет доступен только одному потоку.
(Поскольку вы указываете, что нет межпроцессного взаимодействия, я бы предложил пойти по этому пути.)
Несколько процессов: Вы должны использовать некоторый механизм блокировки, который виден всем процессам, например, файл блокирует, который вы предлагаете.
И то и другое: Используйте оба механизма. Попробуйте заблокировать файл только на абсолютное минимальное количество времени.
Боковой узел:
Первое правило многопоточного программирования — это не «использовать мьютекс», а «стараться не обращаться к данным несколькими потоками» или даже «стараться не использовать несколько потоков, если это абсолютно не необходимо из соображений производительности» (например, вы можете всегда обходиться без потоков для асинхронных операций).