22、文件锁机制:文件锁的概念、flock函数、fcntl函数、多进程文件同步

文件锁这个东西,说白了就是给文件加把锁。

你想想看,多个进程同时读写一个文件,会出什么事?数据错乱、内容覆盖、甚至文件损坏。我在嵌入式项目里就吃过这个亏——两个进程同时写日志,结果日志文件里全是乱码,排查了半天才发现是同步问题。

嗯,今天我们就来聊聊文件锁。我会从概念讲起,然后带你看看两个最常用的锁函数:flockfcntl,最后聊聊多进程怎么用锁做文件同步。

文件锁的概念

文件锁是一种同步机制。它让多个进程在访问同一个文件时,能按顺序来,而不是一窝蜂往上冲。

锁分两种:

  • 共享锁(读锁):多个进程可以同时加读锁。大家都能读,但不能写。
  • 独占锁(写锁):只有一个进程能加写锁。加了写锁,别人既不能读也不能写。

我习惯把共享锁叫「看门锁」——只让看,不让动。独占锁叫「施工锁」——锁上门,里面在施工,谁也别进来。

核心原则:读锁可共享,写锁必独占。

flock 函数

flock 是 BSD 系统提供的文件锁函数。它简单粗暴,用起来很直接。

#include <sys/file.h>

int flock(int fd, int operation);

参数 operation 有几种:

含义
LOCK_SH加共享锁
LOCK_EX加独占锁
LOCK_UN解锁
LOCK_NB非阻塞模式(常与上面组合使用)

举个例子:

int fd = open("data.txt", O_RDWR);
if (fd == -1) {
    perror("open");
    return -1;
}

// 加独占锁
if (flock(fd, LOCK_EX) == -1) {
    perror("flock");
    close(fd);
    return -1;
}

// 写文件...
write(fd, "hello", 5);

// 解锁
flock(fd, LOCK_UN);
close(fd);

我的经验flock 有个坑——它锁的是文件描述符,不是文件本身。如果你用 dup 复制了 fd,两个 fd 共享同一个锁。我曾经因为这个踩过坑,调试了一下午才发现。

fcntl 函数

fcntl 是 POSIX 标准提供的文件锁函数。它比 flock 更强大,也更复杂。

#include <fcntl.h>

int fcntl(int fd, int cmd, struct flock *lock);

struct flock 结构体长这样:

struct flock {
    short l_type;   // F_RDLCK(读锁), F_WRLCK(写锁), F_UNLCK(解锁)
    short l_whence; // SEEK_SET, SEEK_CUR, SEEK_END
    off_t l_start;  // 起始偏移
    off_t l_len;    // 长度(0 表示到文件末尾)
    pid_t l_pid;    // 持有锁的进程 ID(仅用于 F_GETLK)
};

fcntl 支持三种命令:

  • F_SETLK:设置锁(非阻塞)
  • F_SETLKW:设置锁(阻塞)
  • F_GETLK:获取锁信息

看个例子:

struct flock lock;
lock.l_type = F_WRLCK;   // 写锁
lock.l_whence = SEEK_SET;
lock.l_start = 0;
lock.l_len = 0;           // 锁整个文件

int fd = open("data.txt", O_RDWR);
if (fcntl(fd, F_SETLKW, &lock) == -1) {
    perror("fcntl");
    close(fd);
    return -1;
}

// 写文件...
write(fd, "world", 5);

// 解锁
lock.l_type = F_UNLCK;
fcntl(fd, F_SETLK, &lock);
close(fd);

注意fcntl 锁与进程关联。如果进程退出,锁会自动释放。但如果你 fork 了子进程,子进程不会继承父进程的锁——这一点和 flock 不同。

flock 与 fcntl 的区别

特性flockfcntl
标准BSDPOSIX
锁粒度整个文件文件区域(可部分加锁)
与 fd 关系锁与 fd 关联锁与进程关联
fork 后行为子进程继承锁子进程不继承锁
NFS 支持部分支持支持

我个人习惯:简单场景用 flock,复杂场景用 fcntl。比如只需要锁整个文件,flock 一行搞定。如果需要锁文件的一部分,那就得上 fcntl

多进程文件同步

多进程同步,说白了就是让多个进程有序地访问文件。没有锁,就是一片混乱。

我画了一张图,帮你理解多进程文件锁的流程:

多进程文件锁同步流程 进程 A 进程 B 文件锁(互斥访问) 同一时刻,只有一个进程能获得写锁 共享文件 请求锁 获得锁,开始读写 等待锁释放

实际项目中,我常用这种模式:

// 进程 A
int fd = open("shared.dat", O_RDWR | O_CREAT, 0644);
flock(fd, LOCK_EX);  // 等锁
// 读写文件...
flock(fd, LOCK_UN);
close(fd);

// 进程 B
int fd = open("shared.dat", O_RDWR);
flock(fd, LOCK_EX);  // 等锁(如果 A 没释放,这里会阻塞)
// 读写文件...
flock(fd, LOCK_UN);
close(fd);

避坑指南:我曾经在项目中用 flock 做日志同步,结果发现进程意外崩溃时,锁没有释放。后来加了信号处理,在 SIGTERMSIGINT 里主动解锁。嗯,这一点很重要——锁一定要在异常路径上也释放掉。

实际应用场景

  • 日志文件:多个进程写同一个日志文件,用写锁保证日志不乱。
  • 配置文件:读配置时加读锁,写配置时加写锁。
  • 数据库文件:简单的键值存储,用文件锁做并发控制。
  • 锁文件:用 /var/run/app.pid 这种锁文件,防止程序重复启动。

最后说一句:文件锁不是万能的。如果你需要高性能的并发控制,还是得上数据库或者专门的同步机制。但对于嵌入式系统、简单工具脚本来说,文件锁够用了。

总结flock 简单,fcntl 灵活。选哪个,看你的场景。记住一点——加锁要成对,解锁要及时。

公众号:蓝海资料掘金营,微信 deep3321