先说结论
lseek
+ SET_SEEK_END
将文件指针置到最后后,无法保证写入时仍然在最后(即,指针移动和写入两个动作不是原子的),如果有多个进程打开同一个文件读写,则有可能发生覆盖
open
+ O_APPEND
打开文件的话,每次write时,都会自动先移动指针,然后写入,可以视为原子操作。多个进程可以安全的追加写同一个文件。
lseek + SET_SEEK_END 实验
lseek1
打开一个文件,设置文件指针到末尾,等待20秒后写入字符串“hello”
/* lseek1.c */
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
int main()
{
/* 打开文件 */
int fd = open("./testfile.txt", O_RDWR);
if (fd == -1) {
perror("Open testfile.txt failed");
return 1;
}
/* 设置文件指针到末尾 */
off_t off = lseek(fd, 0, SEEK_END);
if (off == -1) {
perror("lseek failed");
return 1;
}
/* 休眠,模拟进程竞争,中途拉起lseek2进程 */
sleep(20);
/* 写入 “hello” */
ssize_t wb = write(fd, "hello", sizeof("hello"));
if (wb != sizeof("hello")) {
perror("write failed");
return 1;
}
close(fd);
return 0;
}
lseek2
与 lseek1
几乎一样,区别在于他写入 “what?”
/* lseek2.c */
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
int main()
{
/* 打开文件 */
int fd = open("./testfile.txt", O_RDWR);
if (fd == -1) {
perror("Open testfile.txt failed");
return 1;
}
/* 设置文件指针到末尾 */
off_t off = lseek(fd, 0, SEEK_END);
if (off == -1) {
perror("lseek failed");
return 1;
}
/* 写入 “what?” */
ssize_t wb = write(fd, "what?", sizeof("what?"));
if (wb != sizeof("what?")) {
perror("write failed");
return 1;
}
close(fd);
return 0;
}
先运行 lseek1,在 lseek1 休眠期间 运行 lseek2:
# 文件原来的内容是“0123456789”
velscode@velscode:~/code/blog_temp$ cat testfile.txt
0123456789
# 先运行 lseek1
velscode@velscode:~/code/blog_temp$ ./lseek1 &
[1] 50747
# lseek 休眠期间运行 lseek2
velscode@velscode:~/code/blog_temp$ ./lseek2
# 查看文件内容,已被写入“what?”
velscode@velscode:~/code/blog_temp$ cat testfile.txt
0123456789what?
# lseek1 休眠结束,执行写入
velscode@velscode:~/code/blog_temp$
[1]+ Done ./lseek1
# 再次查看文件内容,发现“waht?”被覆盖写成了“hello”
velscode@velscode:~/code/blog_temp$ cat testfile.txt
0123456789hello
so bad
open() + O_APPEND 实验
open1
打开一个文件,设置文件指针到末尾,等待20秒后写入字符串“hello”
/* open1.c */
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
int main()
{
int fd = open("./testfile.txt", O_RDWR | O_APPEND);
if (fd == -1) {
perror("Open testfile.txt failed");
return 1;
}
sleep(20);
ssize_t wb = write(fd, "hello", sizeof("hello"));
if (wb != sizeof("hello")) {
perror("write failed");
return 1;
}
close(fd);
return 0;
}
open2
与 open1
几乎一样,区别在于他写入 “what?”
/* open2.c */
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
int main()
{
int fd = open("./testfile.txt", O_RDWR | O_APPEND);
if (fd == -1) {
perror("Open testfile.txt failed");
return 1;
}
ssize_t wb = write(fd, "what?", sizeof("what?"));
if (wb != sizeof("what?")) {
perror("write failed");
return 1;
}
close(fd);
return 0;
}
# testfile.txt 原来的内容是“01234567890”
velscode@velscode:~/code/blog_temp$ cat testfile.txt
0123456789
# 拉起open1进程
0123456789velscode@velscode:~/code/blog_temp$ ./open1 &
[1] 50798
# 在open1休眠期间,执行open2
velscode@velscode:~/code/blog_temp$ ./open2
# open2成功追加写入“what?”
velscode@velscode:~/code/blog_temp$ cat testfile.txt
0123456789what?
# open1休眠完毕,开始写入
velscode@velscode:~/code/blog_temp$
[1]+ Done ./open1
# open1在open2的字符串后面追加写入
velscode@velscode:~/code/blog_temp$ cat testfile.txt
0123456789what?hello
其他注意事项
仅在POSIX系统上,打开带有O_APPEND标志的文件将保证附加写入始终是安全的。
两个进程通过O_APPEND追加写入同一个文件,虽然能够保证字节顺序,但是不一定是连续的,即,有可能发生交错
参考资料
[1] Appending to a File from Multiple Processes
[2] lseek(2) — Linux manual page
[3] open(2) — Linux manual page