Это неправильный вид блокировки. flock
- это только для блокировки между процессами, а не между потоками в одном и том же процессе. От man 2 flock
:
A call to flock() may block if an incompatible lock is held by <b>another
process.</b> To make a nonblocking request, include LOCK_NB (by ORing)
with any of the above operations.
Акцент добавлен. И ...
A process may only hold one type of lock (shared or exclusive) on a
file. <b>Subsequent flock() calls on an already locked file will convert
an existing lock to the new lock mode.</b>
Вместо этого вы хотите использовать flockfile
(или дополнительно, если также используется несколько процессов). Функция flockfile
, которая используется для управления доступом к FILE *
из нескольких потоков. Со страницы руководства:
The stdio functions are thread-safe. This is achieved by assigning to
each FILE object a lockcount and (if the lockcount is nonzero) an own‐
ing thread. For each library call, these functions wait until the FILE
object is no longer locked by a different thread, then lock it, do the
requested I/O, and unlock the object again.
(Note: this locking has nothing to do with the file locking done by
functions like flock(2) and lockf(3).)
Как это:
// in one of the threads...
flockfile(fp);
fwrite(..., fp);
funlockfile(fp);
Хорошая новость заключается в том, что на glibc
вам не нужно блокировать файл, если у вас есть только один вызов функции из stdio.h в каждом критическом разделе, поскольку glibc
имеет fwrite
, который блокирует. Но это не верно для других платформ, и, конечно, это не повредит блокировать файл. Поэтому, если вы работаете в Linux, вы никогда бы не заметили, что flock
делает не то, что вам нужно, поскольку fwrite
делает это автоматически.
О режиме добавления: Вам не нужны дополнительные очистки при записи в режиме добавления, если только вы не хотите обеспечить упорядочение между различными процессами, в которых открыт один и тот же файл (или одним процессом с несколькими дескрипторами для одного файл). Вам не нужен режим «+», если вы не читаете из файла.
Демонстрация flock
Если вы мне не верите, что flock
НЕ обеспечивает безопасность потоков между потоками, использующими один и тот же файловый дескриптор, вот демонстрационная программа.
#include <stdio.h>
#include <errno.h>
#include <pthread.h>
#include <string.h>
#include <stdlib.h>
#include <sys/file.h>
static FILE *fp;
static pthread_mutex_t mutex;
static pthread_cond_t cond;
int state;
static void fail_func(int code, const char *func, int line)
{
fprintf(stderr, "%s:%d: error: %s\n", func, line, strerror(code));
exit(1);
}
#define fail(code) fail_func(code, __FUNCTION__, __LINE__)
void *thread1(void *p)
{
int r;
// Lock file (thread 2 does not have lock yet)
r = pthread_mutex_lock(&mutex);
if (r) fail(r);
r = flock(fileno(fp), LOCK_EX);
if (r) fail(errno);
puts("thread1: flock successful");
state = 1;
r = pthread_mutex_unlock(&mutex);
if (r) fail(r);
// Wake thread 2
r = pthread_cond_signal(&cond);
if (r) fail(r);
// Wait for thread 2
r = pthread_mutex_lock(&mutex);
if (r) fail(r);
while (state != 2) {
r = pthread_cond_wait(&cond, &mutex);
if (r) fail(r);
}
puts("thread1: exiting");
r = pthread_mutex_unlock(&mutex);
if (r) fail(r);
return NULL;
}
void *thread2(void *p)
{
int r;
// Wait for thread 1
r = pthread_mutex_lock(&mutex);
if (r) fail(r);
while (state != 1) {
r = pthread_cond_wait(&cond, &mutex);
if (r) fail(r);
}
// Also lock file (thread 1 already has lock)
r = flock(fileno(fp), LOCK_EX);
if (r) fail(r);
puts("thread2: flock successful");
// Wake thread 1
state = 2;
puts("thread2: exiting");
r = pthread_mutex_unlock(&mutex);
if (r) fail(r);
r = pthread_cond_signal(&cond);
if (r) fail(r);
return NULL;
}
int main(int argc, char *argv[])
{
pthread_t t1, t2;
void *ret;
int r;
r = pthread_mutex_init(&mutex, NULL);
if (r) fail(r);
r = pthread_cond_init(&cond, NULL);
if (r) fail(r);
fp = fopen("flockfile.txt", "a");
if (!fp) fail(errno);
r = pthread_create(&t1, NULL, thread1, NULL);
if (r) fail(r);
r = pthread_create(&t2, NULL, thread2, NULL);
if (r) fail(r);
r = pthread_join(t1, &ret);
if (r) fail(r);
r = pthread_join(t2, &ret);
if (r) fail(r);
puts("done");
return 0;
}
В моей системе выдает следующий вывод:
thread1: flock successful
thread2: flock successful
thread2: exiting
thread1: exiting
done
Обратите внимание, что поток 1 не освобождает flock
, и поток 2 может получить его в любом случае. Использование условной переменной гарантирует, что поток 1 не завершится, пока поток 2 не получит блокировку. Это именно то, что говорит справочная страница flock
, , потому что flock
говорит, что блокировки для каждого файла и для процесса, но НЕ для потока.
Сводка для атомарного добавления в файл
Чтобы сделать атомарную запись между процессами и потоками, вы можете сделать одну из двух простых вещей:
Используйте write
и записывайте не более PIPE_BUF
байтов. PIPE_BUF
определено в <limits.h>
, в моей системе это 4096. Если файловый дескриптор открыт в режиме O_APPEND
, тогда запись будет происходить атомарно до конца файла, независимо от того, кто еще пишет в файл (темы и / или процессы).
Используйте write
и flock
. Если вы когда-либо записываете больше, чем PIPE_BUF
байт за раз, это единственный вариант для всех записей. Опять же, если файл открыт в режиме O_APPEND
, то байты будут идти до конца файла. Это произойдет атомарно, но только с точки зрения каждого с flock
.
Дополнительно
Если вы используете <stdio.h>
и совместно используете FILE *
между потоками, вам также нужно будет вызывать flockfile
из каждого потока. В этом нет необходимости, если вы используете низкоуровневый API POSIX (open
/ write
/ etc). Это также не требуется, если вы используете glibc
и каждая запись представляет собой один вызов функции (например, вы хотите атомарно fputs
).
Если вы используете только один процесс, flock
не требуется.