gpt4 book ai didi

c - 在 fopen a+ 模式下从多个线程/进程写入锁定文件之前是否需要同步/刷新?

转载 作者:太空宇宙 更新时间:2023-11-04 00:54:07 26 4
gpt4 key购买 nike

我正在从多个线程对单个文件执行 I/O。对该共享文件 foo 的访问是通过建议文件锁(flock(2)LOCK_EX)控制的。 foo 是用 fopen(3) 模式 a+ 打开的。选择 a+ 是因为文档说明:

Subsequent writes to the file will always end up at the then current end of file, irrespective of any intervening fseek(3) or similar.

简化后,操作将开始:

FILE *fp = fopen("foo", "a+");
...spawn threads...

写作将继续:

flock(fileno(fp), LOCK_EX);
fwrite(buffer, buffer_size, 1, fp);
flock(fileno(fp), LOCK_UN);

我目前在 fwrite(3) 之前没有任何 fflush(3)fsync(2) 调用,我想知道是否我应该。 fopen(3) a+ 模式在计算“当前 EOF”时是否考虑了多个线程命中文件?我知道 flock(2) 在有未完成的 I/O 时授予我锁可能没有问题。

在我的有限测试中(在多个线程中写入很长的 ASCII 文本行,然后换行很多秒,然后确保结果文件中每行的字符数相等),我没有看到任何“损坏"当不使用 fflush(3)fsync(2) 时。它们的存在大大降低了 I/O 性能。

长话短说:使用文件锁时,是否需要在写入以 a+ 模式打开的多个线程之间的共享文件之前刷新流?多个 fork /不同的机器写入文件并行文件系统?

可能相关: why fseek or fflush is always required between reading and writing in the read/write "+" modes

最佳答案

那是一种错误的锁。 flock 用于进程之间的锁定,而不是同一进程中的线程之间。来自 man 2 flock :

A call to flock() may block if an incompatible lock is held by  anotherprocess.   To  make  a  nonblocking request, include LOCK_NB (by ORing)with any of the above operations.

Emphasis added. And...

A process may only hold one type of lock (shared  or  exclusive)  on  afile.   Subsequent flock() calls on an already locked file will convertan existing lock to the new lock mode.

You want to use flockfile instead (or additionally, if using multiple processes as well). The flockfile function which is used for controlling access to a FILE * from multiple threads. From the man page:

The stdio functions are thread-safe.  This is achieved by assigning  toeach  FILE object a lockcount and (if the lockcount is nonzero) an own‐ing thread.  For each library call, these functions wait until the FILEobject  is no longer locked by a different thread, then lock it, do therequested I/O, and unlock the object again.(Note: this locking has nothing to do with the  file  locking  done  byfunctions like flock(2) and lockf(3).)

Like this:

// in one of the threads...
flockfile(fp);
fwrite(..., fp);
funlockfile(fp);

好消息是 glibc ,如果在每个关键部分只有一个来自 stdio.h 的函数调用,则不需要锁定文件,因为 glibc有一个 fwrite那个锁。但这在其他平台上并非如此,锁定文件当然也无妨。所以如果你在 Linux 上运行,你永远不会注意到 flock没有做你想做的事,因为fwrite自动执行。

关于追加模式:在使用追加模式写入时不需要额外的刷新,除非你想确保打开相同文件的不同进程之间的顺序(或一个进程有多个句柄的同一个文件)。除非从文件中读取,否则不需要“a+”模式。

演示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 successfulthread2: flock successfulthread2: exitingthread1: exitingdone

请注意,线程 1 不会释放 flock ,并且线程 2 无论如何都能够获取它。使用条件变量可确保线程 1 在线程 2 获得锁之前不会退出。 这正是flock手册页说, 因为 flock表示锁是针对每个文件和每个进程的,但不是针对每个线程的。

原子追加到文件的总结

为了在进程和线程之间进行原子写入,您可以执行以下两个简单操作之一:

  • 使用 write并且不超过 PIPE_BUF字节。 PIPE_BUF<limits.h> 中定义,在我的系统上它是 4096。如果文件描述符在 O_APPEND 中打开模式,那么无论其他人(线程和/或进程)正在写入文件(线程和/或进程),写操作都将自动执行到文件末尾。

  • 使用 writeflock .如果你曾经写的超过PIPE_BUF一次字节,这是所有写入的唯一选择。同样,如果文件在 O_APPEND 中打开模式,则字节将转到文件末尾。这将以原子方式发生,但仅从每个拥有 flock 的人的角度来看.

此外,

  • 如果您使用 <stdio.h>并分享一个FILE *在线程之间,您还需要调用 flockfile从每个线程。如果您使用较低级别的 POSIX API ( open/write/etc),则不需要这样做。如果您使用 glibc,也不需要这样做并且每次写入都是一个函数调用(例如,您想自动执行 fputs )。

  • 如果你只使用一个进程,flock不需要。

关于c - 在 fopen a+ 模式下从多个线程/进程写入锁定文件之前是否需要同步/刷新?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8717490/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com