flock是对整个文件施加的建议性锁,与文件描述符绑定,通过close()自动释放,不支持字节范围锁定,且在nfsv3等文件系统上不可用。

Linux 下用 flock 对已打开的文件描述符加锁
只要文件描述符是通过 open() 获得的(且未被 dup() 或 fork() 复制导致语义混淆),flock() 就能直接作用于它。它不关心文件是否“正在被打开”,只关心 fd 是否有效、是否指向一个支持 flock 的文件系统(如 ext4、XFS;不支持 NFSv3 及更早版本)。
常见错误是试图对 stdin/stdout 或管道 fd 调用 flock,会返回 EBADF;另一个坑是没检查返回值——flock(fd, LOCK_EX) 阻塞时看似卡住,其实是正常行为,但若传入了 LOCK_NB 却忽略 EWOULDBLOCK,就会误判为失败。
-
flock()是建议性锁(advisory),所有协作进程必须主动调用才能生效,内核不强制拦截读写 - 锁与 fd 绑定,
close(fd)会自动释放锁;dup(fd)不增加锁计数,但两个 fd 都 close 后锁才释放 - 避免在多线程中混用同一 fd:
flock锁的是文件,不是线程,但多个线程对同一 fd 并发调用flock可能因竞态导致逻辑混乱
Windows 下用 LockFile 锁住已映射或已打开的句柄
Windows 的 LockFile(及其带超时的变体 LockFileEx)要求句柄必须以 FILE_SHARE_READ 和/或 FILE_SHARE_WRITE 打开,且不能是控制台句柄、标准流句柄或某些设备句柄。它锁的是文件偏移区间,不是整个文件——这点和 flock 本质不同。
典型错误是传入由 fopen() 得到的 FILE*,必须先用 _get_osfhandle() 转成 HANDLE;另一个常见疏漏是没处理重叠 I/O 场景:LockFileEx 在异步模式下需配合 OVERLAPPED 结构,否则调用会失败并返回 ERROR_INVALID_PARAMETER。
-
LockFile(h, 0, 0, 0xFFFFFFFF, 0xFFFFFFFF)表示锁定整个文件(4GB × 4GB 区域,覆盖 32 位偏移全范围) - 锁是强制性的(mandatory),但仅当卷启用了“强制锁定”策略(
fsutil behavior set disablelastaccess 1不相关,真正开关是fsutil behavior set lockvolume 1,且需 NTFS 格式) - 锁不会随
CloseHandle()自动释放?错——会释放。但若进程异常终止,系统会回收句柄并释放锁,无需手动清理
跨平台封装时为什么别直接封装 flock 和 LockFile
二者语义差异太大:flock 是 fd 级、整文件、建议性、继承性锁;LockFile 是 HANDLE 级、字节区间、可强制、不可继承(默认)。强行统一接口容易掩盖行为差异,比如在 Windows 上用 flock-like 接口调用 LockFileEx 时忽略 lpOverlapped,会导致同步锁行为在高并发下卡死或失败。
更稳妥的做法是分层:底层保留原生调用,上层按使用场景抽象——例如日志模块需要“独占写入”,就封装为 acquire_log_writer_lock(),内部根据平台选 flock(fd, LOCK_EX) 或 LockFileEx(h, LOCKFILE_EXCLUSIVE_LOCK, ...);而数据库 WAL 日志可能需要区间锁,则直接暴露平台原生接口,不强行抹平。
- 不要假设
flock在 NFS 上可用——它在 NFSv4 默认启用,但 v3 基本无效;LockFile在 SMB 共享上也受限,需确认服务器支持 byte-range locking - C++ RAII 封装锁时,析构函数里调用
close()或CloseHandle()即可释放,无需额外 unlock 调用(flock没 unlock 函数,UnlockFile是可选的,close 已足够) - 测试时务必覆盖进程崩溃场景:用
kill -9或 Windows 任务管理器结束进程,验证锁是否如期释放
为什么 std::fstream 无法直接加锁
因为 std::fstream 封装的是 C++ 流抽象,其底层 HANDLE 或 fd 对用户不可见,且标准库未提供任何锁接口。试图对 std::fstream::rdbuf()->pubseekoff() 后调用 flock 会失败——你拿不到它的 fd;Windows 下用 _get_osfhandle(_fileno(fstream.rdbuf()->_file())) 也不可靠,因某些实现可能缓冲或复用句柄。
正确做法是绕过流对象,用原始系统调用打开文件,再把 fd/HANDLE 传给 flock 或 LockFile。如果必须用 std::fstream 读写,应在加锁后,用 fdopen()(Linux)或 _fdopen()(Windows)桥接,但要注意:C++ 流的缓冲行为可能导致写入延迟,锁住的只是“打开动作”,不是“数据落盘时机”。
- Linux 示例:
int fd = open("log.txt", O_RDWR | O_CREAT, 0644); flock(fd, LOCK_EX); FILE* fp = fdopen(fd, "a");—— 此时fp写入受锁保护,但需注意fflush(fp)确保立即落盘 - Windows 示例:
HANDLE h = CreateFile(L"log.txt", GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ, nullptr, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); LockFileEx(h, LOCKFILE_EXCLUSIVE_LOCK, 0, 0xFFFFFFFF, 0xFFFFFFFF, &overlapped); - 最易忽略的一点:锁只防止其他进程修改文件内容,不保证你自己的多个线程对同一
std::fstream的并发 write 是线程安全的——那属于流对象内部状态竞争,和文件锁无关
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











