在c++多进程或多线程的开发场景中,多个执行流同时读写同一个文件时,很容易出现数据覆盖、内容错乱的问题,文件锁定是解决这类并发修改问题的有效手段。不同操作系统提供了不同的文件锁定接口,Linux系统常用flock函数,Windows系统常用LockFile函数,下面分别介绍两种方式的实现方法。

Linux系统下使用flock实现文件锁定
flock是Linux系统提供的文件锁定系统调用,作用是对打开的文件描述符施加建议性锁,锁的粒度是整个文件,使用起来比较简单。
flock函数原型
flock的函数定义在<sys/file.h>头文件中,原型如下:
#include <sys/file.h> int flock(int fd, int operation);
参数说明:
- fd:已经打开的文件描述符,需要通过open函数先打开目标文件获取
- operation:锁的操作类型,可选值如下:
- LOCK_SH:施加共享锁,多个进程可以同时持有共享锁,适合读操作场景
- LOCK_EX:施加排他锁,同一时间只能有一个进程持有排他锁,适合写操作场景
- LOCK_UN:释放锁
- LOCK_NB:非阻塞模式,和上述三种操作按位或组合使用,获取锁失败时直接返回不阻塞
返回值:成功返回0,失败返回-1并设置errno。
flock使用示例
下面是一个使用flock实现写文件时加排他锁的完整示例:
#include <iostream>
#include <fcntl.h>
#include <sys/file.h>
#include <unistd.h>
#include <string.h>
int main() {
// 打开目标文件,不存在则创建,权限为0644
int fd = open("test.txt", O_RDWR | O_CREAT, 0644);
if (fd == -1) {
std::cerr << "打开文件失败" << std::endl;
return 1;
}
// 施加排他锁,阻塞模式等待锁释放
if (flock(fd, LOCK_EX) == -1) {
std::cerr << "加锁失败" << std::endl;
close(fd);
return 1;
}
std::cout << "获取排他锁成功,开始写入文件" << std::endl;
// 执行文件写入操作
const char* content = "这是并发安全的写入内容n";
write(fd, content, strlen(content));
// 模拟业务处理耗时
sleep(5);
// 释放锁
flock(fd, LOCK_UN);
std::cout << "写入完成,锁已释放" << std::endl;
close(fd);
return 0;
}
注意事项:flock施加的是建议性锁,也就是说如果其他进程不调用flock尝试加锁,直接操作文件依然可以修改内容,所以所有操作文件的进程都需要遵循加锁的规则才能保证效果。另外flock的锁在进程退出时会自动释放,不需要手动释放也可以避免死锁。
Windows系统下使用LockFile实现文件锁定
Windows系统没有提供flock接口,对应的文件锁定函数是LockFile,属于Windows API的一部分,需要包含<windows.h>头文件,锁的粒度可以指定文件的偏移量和长度,比flock更灵活。
LockFile函数原型
LockFile的函数原型如下:
BOOL LockFile( HANDLE hFile, DWORD dwFileOffsetLow, DWORD dwFileOffsetHigh, DWORD nNumberOfBytesToLockLow, DWORD nNumberOfBytesToLockHigh );
参数说明:
- hFile:打开的文件句柄,通过CreateFile函数获取
- dwFileOffsetLow:锁定区域起始偏移量的低32位
- dwFileOffsetHigh:锁定区域起始偏移量的高32位,通常设置为0即可
- nNumberOfBytesToLockLow:锁定区域长度的低32位
- nNumberOfBytesToLockHigh:锁定区域长度的高32位,通常设置为0即可
返回值:成功返回非零值,失败返回0,可以通过GetLastError获取错误原因。对应的解锁函数是UnlockFile,参数和LockFile完全一致。
LockFile使用示例
下面是一个使用LockFile实现文件锁定的完整示例:
#include <iostream>
#include <windows.h>
int main() {
// 打开目标文件,不存在则创建
HANDLE hFile = CreateFile(
"test.txt",
GENERIC_READ | GENERIC_WRITE,
0, // 不共享,其他进程无法同时打开该文件
NULL,
OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL
);
if (hFile == INVALID_HANDLE_VALUE) {
std::cerr << "打开文件失败,错误码:" << GetLastError() << std::endl;
return 1;
}
// 锁定整个文件,偏移量0,长度0xFFFFFFFF表示锁定到文件末尾
if (LockFile(hFile, 0, 0, 0xFFFFFFFF, 0) == 0) {
std::cerr << "加锁失败,错误码:" << GetLastError() << std::endl;
CloseHandle(hFile);
return 1;
}
std::cout << "获取文件锁成功,开始写入文件" << std::endl;
// 执行文件写入操作
const char* content = "这是Windows下并发安全的写入内容n";
DWORD bytesWritten;
WriteFile(hFile, content, strlen(content), &bytesWritten, NULL);
// 模拟业务处理耗时
Sleep(5000);
// 解锁文件
UnlockFile(hFile, 0, 0, 0xFFFFFFFF, 0);
std::cout << "写入完成,锁已释放" << std::endl;
CloseHandle(hFile);
return 0;
}
注意事项:LockFile施加的是强制锁,只要文件被锁定,其他进程即使不调用LockFile也无法修改被锁定的区域。另外LockFile的锁不会随进程退出自动释放,如果进程异常退出没有调用UnlockFile,锁会一直存在,所以需要在代码中做好异常处理,确保锁能被正常释放。
两种方式对比与选择建议
两种方式的核心差异如下:
| 对比项 | flock | LockFile |
|---|---|---|
| 适用系统 | Linux/Unix类系统 | Windows系统 |
| 锁粒度 | 整个文件 | 可指定文件区域 |
| 锁类型 | 建议性锁 | 强制性锁 |
| 自动释放 | 进程退出自动释放 | 需要手动调用UnlockFile释放 |
选择建议:如果是Linux平台开发优先使用flock,实现简单且不需要手动处理锁释放;如果是Windows平台开发使用LockFile,注意做好异常场景下的锁释放逻辑。如果是跨平台项目,可以通过条件编译区分不同系统的实现,保证文件锁定的兼容性。