POSIX 文件锁机制
文件锁(File Locking)是多进程编程中一个常见而又容易被误用的概念。它能防止多个进程同时修改同一文件而导致数据损坏。在 Unix/Linux 系统中,文件锁主要有两种机制:
flock()锁:BSD 风格,锁定整个文件,操作简单。fcntl()锁:POSIX 标准锁,支持对文件的任意区域加锁,更灵活。
本文重点介绍 POSIX 文件锁(fcntl) 的工作原理、使用方式和典型问题。
一、POSIX 文件锁的基本原理
1. 锁的类型
POSIX 文件锁支持两种类型:
| 锁类型 | 宏定义 | 意义 |
|---|---|---|
| 共享锁 | F_RDLCK | 允许多个进程同时读取文件 |
| 排他锁 | F_WRLCK | 仅允许一个进程写入文件 |
| 解锁 | F_UNLCK | 释放锁 |
共享锁和排他锁不能共存,同一区域内只能有一种锁类型。
2. 锁是“建议性”的(Advisory Lock)
POSIX 文件锁是 advisory lock(建议锁),而非强制锁。
这意味着:
操作系统不会强制阻止其他进程访问文件;
只有遵守锁协议(即也使用
fcntl())的进程才会“尊重”锁。
换句话说,文件锁是进程间的一种约定。
3. 锁的范围
fcntl 可以锁定 整个文件,也可以锁定 文件的部分区域。
锁定范围通过结构体 struct flock 指定:
struct flock {short l_type; // 锁类型: F_RDLCK, F_WRLCK, F_UNLCKshort l_whence; // 起始位置参考: SEEK_SET, SEEK_CUR, SEEK_ENDoff_t l_start; // 相对于 l_whence 的偏移off_t l_len; // 锁定的长度, 0 表示到文件结尾pid_t l_pid; // 持锁进程的PID (仅用于 F_GETLK)
};
二、使用方法
1. 加锁与解锁
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>int main() {int fd = open("data.txt", O_RDWR | O_CREAT, 0666);if (fd < 0) {perror("open");return 1;}struct flock lock;memset(&lock, 0, sizeof(lock));lock.l_type = F_WRLCK; // 排他锁lock.l_whence = SEEK_SET;lock.l_start = 0;lock.l_len = 0; // 锁整个文件printf("尝试加锁...\n");if (fcntl(fd, F_SETLKW, &lock) == -1) {perror("fcntl - lock");return 1;}printf("加锁成功!正在写入文件...\n");write(fd, "Hello POSIX Lock\n", 17);sleep(10); // 模拟长时间操作// 解锁lock.l_type = F_UNLCK;fcntl(fd, F_SETLK, &lock);printf("已解锁。\n");close(fd);return 0;
}
F_SETLK:非阻塞加锁,如果无法立即加锁则返回 -1。F_SETLKW:阻塞加锁(等待锁释放后再加锁)。
2. 检查锁状态
可使用 F_GETLK 查询指定区域是否被锁:
lock.l_type = F_WRLCK;
fcntl(fd, F_GETLK, &lock);
if (lock.l_type != F_UNLCK) {printf("文件被进程 %d 锁定\n", lock.l_pid);
}
三、fcntl vs flock
| 对比项 | fcntl | flock |
|---|---|---|
| 标准 | POSIX | BSD |
| 锁粒度 | 可锁部分区域 | 仅整文件 |
| 锁继承 | 文件描述符级(同进程不同fd共享) | 进程级 |
| 锁类型 | 建议锁 | 建议锁 |
| 可移植性 | 高 | 一般 |
| 常用场景 | 数据文件、数据库 | 简单日志文件 |
💡 注意:fcntl 锁和 flock 锁是两套机制,互不兼容。
四、锁的生命周期
POSIX 文件锁与 进程和文件描述符 绑定:
当进程退出或关闭文件描述符时,锁会自动释放。
fork() 后,子进程不继承父进程的锁。
同一进程中对同一文件的多个描述符共享锁。
例如:
int fd1 = open("a.txt", O_RDWR);
int fd2 = open("a.txt", O_RDWR);
// 两个fd对应同一文件,但不同锁上下文。
五、常见问题与陷阱
锁不会跨网络共享
NFS 文件系统上的fcntl锁常常不可靠,需使用fcntl(F_SETLK)+ fcntl 协议支持的挂载参数。锁不是线程安全的
POSIX 文件锁是“进程锁”,不是“线程锁”。同一进程内的线程共享锁状态。锁范围重叠的行为
对文件的不同部分加锁时,重叠区域会自动合并为一个更大的锁。读锁与写锁的兼容性
多个读锁可以共存;
读锁与写锁不能同时存在。
六、调试与验证
可以用 lsof 命令查看文件锁信息:
lsof /path/to/file也可以通过 fcntl(F_GETLK) 编程方式检测。
七、总结
POSIX 文件锁是 UNIX 系统中最基础的进程同步机制之一。它:
灵活可配置;
提供区域锁定;
但需要各方遵守锁协议。
建议场景:
多进程同时访问数据库或索引文件;
日志轮转或状态文件更新;
与
flock不兼容的跨平台程序。
不建议场景:
分布式文件系统(NFS、CephFS等);
线程级同步(应使用 pthread 互斥锁)。
八、延伸阅读
man 2 fcntl《Advanced Programming in the UNIX Environment》
POSIX.1-2017 Specification - File Locks
