c++怎么在读写文件时通过RAII机制自动释放文件排他锁【附代码】

大强大大_9812

大强大大_9812

2026-03-24

968人浏览

原创

std::fstream 不提供文件锁管理,需自行封装RAII类调用系统API实现:Windows用CreateFile+LockFileEx,Linux用open+flock;锁必须与句柄/fd绑定,析构时显式解锁并关闭资源。

c++怎么在读写文件时通过raii机制自动释放文件排他锁【附代码】

std::fstream 本身不管理文件锁,RAII 锁必须自己封装

标准 C++ 的 std::fstream 打开文件时不会自动加排他锁(如 Windows 的 LOCKFILE_EXCLUSIVE_LOCK 或 POSIX 的 flock()),更不会在析构时释放。所谓“通过 RAII 自动释放文件锁”,其实是你写一个 RAII 类来包装底层系统调用——std::fstream 只负责 I/O,锁是另一层责任。

常见错误现象:std::ofstream f("data.txt"); 看似“打开即锁定”,其实只是让操作系统分配句柄,其他进程仍可同时读写该文件,Windows 下甚至可能报错 Access is denied,Linux 下则完全无保护。

  • Windows 推荐用 CreateFile() + LockFileEx(),再用 CloseHandle() 自动释放
  • Linux/macOS 用 open() + flock(),锁随 fd 关闭自动释放(但注意:std::fstream::close() 不等于 close() 系统调用)
  • 别试图把 flock() 套在 std::fstream::rdbuf()->fd() 上——该 fd 可能已被库内部关闭或复用,行为未定义

Windows 下用 RAII 封装 CreateFile + LockFileEx

关键点:锁的生命周期必须和句柄(HANDLE)绑定,且锁必须在句柄关闭前显式释放(否则可能阻塞其他进程)。RAII 类应在构造时加锁、析构时解锁 + 关闭句柄。

示例核心逻辑:

class FileLockGuard {
    HANDLE h_;
public:
    FileLockGuard(const char* path) : h_(INVALID_HANDLE_VALUE) {
        h_ = CreateFileA(path, GENERIC_READ | GENERIC_WRITE,
                         0, nullptr, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
        if (h_ == INVALID_HANDLE_VALUE) return;
<pre class="brush:php;toolbar:false;">    OVERLAPPED ol = {};
    if (!LockFileEx(h_, LOCKFILE_EXCLUSIVE_LOCK, 0, 1, 0, &ol)) {
        CloseHandle(h_);
        h_ = INVALID_HANDLE_VALUE;
    }
}

~FileLockGuard() {
    if (h_ != INVALID_HANDLE_VALUE) {
        UnlockFileEx(h_, 0, 1, 0, nullptr);
        CloseHandle(h_);
    }
}

operator bool() const { return h_ != INVALID_HANDLE_VALUE; }

};

使用时:FileLockGuard lock("config.dat"); if (!lock) { /* 失败 */ }。注意:它不提供读写接口,只管锁;后续仍要用 std::fstream 或 WriteFile() 操作同一路径——但要确保没别的进程/线程在碰这个文件。

Linux 下用 RAII 封装 open + flock

POSIX 的 flock() 是建议性锁,依赖所有参与者主动检查,但它和 fd 绑定紧密:只要持有该 fd 的进程存在,锁就有效;fd 关闭即自动释放。所以 RAII 类只需确保 fd 在析构时关闭。

C++14
C++14

C++14 对 C++11 的修正与增强版本,适合旧系统维护和较老工具链兼容。

下载

陷阱比 Windows 更隐蔽:

  • std::fstream 构造时若传入已打开的 fd(如用 std::filebuf::open(int fd, ...)),其内部可能在析构时调用 close(fd),导致锁提前丢失
  • 不要混用 flock() 和 fcntl(F_WRLCK),它们互不感知
  • 多线程中,flock() 是进程级锁,不是线程级——同一进程内多个线程加锁会成功,起不到互斥作用

安全做法是全程绕过 std::fstream,用 read()/write() 直接操作 fd:

class FileLockGuard {
    int fd_;
public:
    FileLockGuard(const char* path) : fd_(-1) {
        fd_ = open(path, O_RDWR | O_CREAT, 0644);
        if (fd_ == -1) return;
        if (flock(fd_, LOCK_EX | LOCK_NB) == -1) {
            close(fd_);
            fd_ = -1;
        }
    }
<pre class="brush:php;toolbar:false;">~FileLockGuard() {
    if (fd_ != -1) {
        flock(fd_, LOCK_UN);
        close(fd_);
    }
}

int fd() const { return fd_; }

};

之后用 write(lock.fd(), buf, len) 写入,避免任何 std::fstream 干预 fd 生命周期。

跨平台?别硬凑,按部署环境选方案

没有标准 C++ 接口能统一 Windows 和 Linux 文件锁语义。强行封装一层抽象(比如定义 ILock 接口)只会掩盖差异,增加误用风险。实际项目中:

  • Windows 服务程序:用 CreateFile+LockFileEx,注意 LOCKFILE_FAIL_IMMEDIATELY 避免阻塞
  • Linux 后台工具:用 flock() + open(),配合 LOCK_NB 做非阻塞尝试
  • 如果必须用 std::fstream,那就放弃排他锁,改用原子重命名(rename("tmp", "final"))+ 临时文件,这是更健壮的替代方案

最常被忽略的一点:文件锁不能防止进程崩溃后残留锁。Windows 下 LockFileEx 的锁在句柄关闭时自动释放,但若进程死锁或异常终止,句柄可能没机会关闭;Linux 下 flock 会随进程终止自动清理——这点反而是 Linux 更可靠。

C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!

相关文章

c++速学教程(入门到精通)
c++速学教程(入门到精通)

c++怎么学习?c++怎么入门?c++在哪学?c++怎么学才快?不用担心,这里为大家提供了c++速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

c++

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
string转int
string转int

在编程中,我们经常会遇到需要将字符串(str)转换为整数(int)的情况。这可能是因为我们需要对字符串进行数值计算,或者需要将用户输入的字符串转换为整数进行处理。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

2023.08.02

7519

3

int占多少字节
int占多少字节

int占4个字节,意味着一个int变量可以存储范围在-2,147,483,648到2,147,483,647之间的整数值,在某些情况下也可能是2个字节或8个字节,int是一种常用的数据类型,用于表示整数,需要根据具体情况选择合适的数据类型,以确保程序的正确性和性能。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2024.08.29

3565

6

c++怎么把double转成int
c++怎么把double转成int

本专题整合了 c++ double相关教程,阅读专题下面的文章了解更多详细内容。

2025.08.29

4688

10

C++中int的含义
C++中int的含义

本专题整合了C++中int相关内容,阅读专题下面的文章了解更多详细内容。

2025.08.29

3145

10

硬盘接口类型介绍
硬盘接口类型介绍

硬盘接口类型有IDE、SATA、SCSI、Fibre Channel、USB、eSATA、mSATA、PCIe等等。详细介绍:1、IDE接口是一种并行接口,主要用于连接硬盘和光驱等设备,它主要有两种类型:ATA和ATAPI,IDE接口已经逐渐被SATA接口;2、SATA接口是一种串行接口,相较于IDE接口,它具有更高的传输速度、更低的功耗和更小的体积;3、SCSI接口等等。

2023.10.19

3248

3

PHP接口编写教程
PHP接口编写教程

本专题整合了PHP接口编写教程,阅读专题下面的文章了解更多详细内容。

2025.10.17

5469

12

php8.4实现接口限流的教程
php8.4实现接口限流的教程

PHP8.4本身不内置限流功能,需借助Redis(令牌桶)或Swoole(漏桶)实现;文件锁因I/O瓶颈、无跨机共享、秒级精度等缺陷不适用高并发场景。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2025.12.29

3909

9

java接口相关教程
java接口相关教程

本专题整合了java接口相关内容,阅读专题下面的文章了解更多详细内容。

2026.01.19

506

15

线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

2023.08.10

4858

6

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
C++ Core Guidelines
C++ Core Guidelines

共0课时 | 0人学习

C++ Reference
C++ Reference

共0课时 | 0人学习

C++ 官方标准说明
C++ 官方标准说明

共0课时 | 0人学习