当前位置: 首页 > article >正文

【文件锁】多进程线程安全访问文件demo

组合文件锁+共享锁,并RAII 化,保证文件的跨进程线程读写安全。
demo模拟使用多个进程,每个进程包含多个线程对文件进行读写测试。
代码调用开源json库,需要下载到调试机器,编译时手动指定:

g++ -std=c++17 -pthread dbug.cpp  -I/root/json/include 
#include <cstddef>
#include <cstdlib>
#include <unistd.h>
#include <fstream>
#include <sys/stat.h>
#include <filesystem>
#include <iomanip>

#include <sys/wait.h>
#include <iostream>
#include <thread>
#include <vector>
#include <chrono>
#include <unordered_map>
#include <memory>
#include <string>
#include <fcntl.h>
#include <sys/file.h> // 包含文件锁相关的头文件
#include <sys/types.h>
#include <sys/wait.h>
#include <mutex>  // 包含mutex头文件
#include <shared_mutex>
#include <nlohmann/json.hpp>  // 引入 JSON 库
#include <stdexcept> // 包含标准异常类

// #include <memory>
/* 写json文件 */
using json = nlohmann::json;
namespace fs = std::filesystem;
void writeToJsonFile(const std::string &filename, const json &data, int maxRetries = 3,
                     int retryDelayMilliseconds = 100);
/* 读json文件 */
json readJsonFromFile(const std::string &filename, int maxRetries = 3,
                      int retryDelayMs = 100);

class FileLock {
//创建双重锁,文件锁实现进程同步,共享锁实现线程同步,设置为配套获取与释放
private:
    std::string fileName;
    int fileDesc;
    mutable std::shared_mutex rwLock; // 实现线程同步
public:
    FileLock(const std::string& file) : fileName(file) {
        // 打开文件(如果文件不存在则创建)
        fileDesc = open(fileName.c_str(), O_CREAT | O_RDWR, 0666);
        if (fileDesc == -1) {
            throw std::runtime_error("Failed to open file for locking: " + fileName);
        }
    }

    ~FileLock() {
        if (fileDesc != -1) {
            close(fileDesc);  // 关闭文件描述符
        }
    }

    // 禁止复制构造函数和赋值运算符
    FileLock(const FileLock&) = delete;
    FileLock& operator=(const FileLock&) = delete;

    // 锁定文件进行读取
    void lockRead() {
        rwLock.lock_shared(); // 获取共享锁(读锁)
        if (flock(fileDesc, LOCK_SH) == -1) { // 获取共享锁(读锁)
            // rwLock.unlock_shared(); // 释放共享锁
            throw std::runtime_error("Failed to lock file for reading: " + fileName);
        }
    }

    // 释放文件读取锁
    void unlockRead() {
        if (flock(fileDesc, LOCK_UN) == -1) { // 释放锁
            throw std::runtime_error("Failed to unlock file after reading: " + fileName);
        }
        rwLock.unlock_shared(); // 释放共享锁
    }

    // 锁定文件进行写入
    void lockWrite() {
        rwLock.lock(); // 获取独占锁(写锁)
        if (flock(fileDesc, LOCK_EX) == -1) { // 获取独占锁(写锁)
            // rwLock.unlock(); // 释放独占锁
            throw std::runtime_error("Failed to lock file for writing: " + fileName);
        }
    }

    // 释放文件写入锁
    void unlockWrite() {
        if (flock(fileDesc, LOCK_UN) == -1) { // 释放锁
            throw std::runtime_error("Failed to unlock file after writing: " + fileName);
        }
        rwLock.unlock(); // 释放独占锁
    }
};

class FileManager {
private:
    // std::unordered_map<std::string, std::shared_ptr<FileLock>> fileLocks;
    std::unordered_map<std::string, FileLock> fileLocks;
    std::mutex managerMtx; // 用于保护 fileLocks 的互斥锁

    // 私有构造函数,禁止外部创建实例
    FileManager() = default;

    // 删除拷贝构造函数和赋值运算符
    FileManager(const FileManager&) = delete;
    FileManager& operator=(const FileManager&) = delete;

public:
    // 获取单例实例
    static FileManager& getInstance() {
        static FileManager instance; // 线程安全的局部静态变量(C++11 及以上)
        return instance;
    }

    // 获取指定文件的锁
    FileLock& getFileLock(const std::string& fileName) {

    // std::shared_ptr<FileLock> getFileLock(const std::string& fileName) {
        std::lock_guard<std::mutex> guard(managerMtx);
        // // std::cout << "getFileLock:" << fileName << std::endl;
        // if (fileLocks.find(fileName) == fileLocks.end()) {
        //     // 如果该文件锁不存在,创建一个新的锁对象
        //     fileLocks[fileName] = FileLock(fileName); // 永久保存FileLock,减少调用
        //     // fileLocks[fileName] = std::make_shared<FileLock>(fileName);//动态释放FileLock
        // }
        // return fileLocks[fileName];
        auto it = fileLocks.find(fileName);
        if (it == fileLocks.end()) {
            // 如果该文件锁不存在,创建一个新的锁对象并插入到 map 中
            it = fileLocks.emplace(fileName, fileName).first;
        }
        return it->second;
    }

    // 移除指定文件的锁(可选)
    void removeFileLock(const std::string& fileName) {
        std::lock_guard<std::mutex> guard(managerMtx);
        fileLocks.erase(fileName);
    }
};
//创建单例锁管理,永久存储文件锁
FileManager& manager = FileManager::getInstance();

//将双重锁 RAII 化, 用于自动管理 FileLock 的写锁
class WriteLockGuard {
public:
    WriteLockGuard(FileLock& fileLock) : fileLock(fileLock) {
        fileLock.lockWrite(); // 加锁
    }

    ~WriteLockGuard() {
        fileLock.unlockWrite(); // 自动解锁
    }

private:
    FileLock& fileLock;
};

//将双重锁 RAII 化, 用于自动管理 FileLock 的读锁
class ReadLockGuard {
public:
    ReadLockGuard(FileLock& fileLock) : fileLock(fileLock) {
        fileLock.lockRead(); // 加锁
    }

    ~ReadLockGuard() {
        fileLock.unlockRead(); // 自动解锁
    }

private:
    FileLock& fileLock;
};

/*******************************************************************************
 * 名称: checkJsonFile
 * 描述: 创建json文件
 * 作者: mkx
 * 参数: void
 * 返回: void
 ******************************************************************************/
void checkJsonFile(const std::string &fileName) {
    try
    {
        // 检查文件是否存在
        if (fs::exists(fileName)) {
            return;
        }
        // 获取文件所在目录
        fs::path filePath(fileName);
        fs::path directory = filePath.parent_path();

        // std::cout << "filePath:" << filePath << std::endl;
        // std::cout << "directory:" << directory << std::endl;

        // 如果目录不存在,则创建目录
        if (!directory.empty() && !fs::exists(directory)) {
                // std::cout << "创建目录: " << directory << std::endl;
            // debug_log(DLOG_INFO, "Directory created: %s", directory.string().c_str());
            if (!fs::create_directories(directory)) {
                std::cerr << "无法创建目录: " << directory << std::endl;
                // debug_log(DLOG_ERROR, "Directory created fail: %s", directory.string().c_str());
                return;
            }
        }

        FileLock& fileLock = manager.getFileLock(fileName);//以文件名为KEY获取对应锁
        //WriteLockGuard加锁时会自动创建文件
        WriteLockGuard lock(fileLock); // 创建 WriteLockGuard 对象,自动加锁
        if (fs::exists(fileName)) {
            // debug_log(DLOG_INFO, "File created: %s", filePath.string().c_str());
            std::cout << "文件创建成功: " << std::endl;
            return;
        }
    }
    catch (const std::filesystem::filesystem_error &e)
    {
        const std::string &errorMessage = e.what();
        // debug_log(DLOG_ERROR, "Error: %s", errorMessage.c_str());
    }
    catch (const std::exception &e)
    {
        const std::string &errorMessage = e.what();
        // debug_log(DLOG_ERROR, "Error: %s", errorMessage.c_str());
    }
}

/*******************************************************************************
 * 名称: writeToJsonFile
 * 描述: 写json文件
 * 作者: mkx
 * 参数: void
 * 返回: void
 ******************************************************************************/
void writeToJsonFile(const std::string &filename, const json &data, int maxRetries,
                     int retryDelayMilliseconds)
{
    checkJsonFile(filename);// 内有双重锁!
    FileLock& fileLock = manager.getFileLock(filename);//以文件名为KEY获取对应锁
    for (int retryCount = 0; retryCount < maxRetries; ++retryCount)
    {
        try
        {
            WriteLockGuard lock(fileLock); // 创建 WriteLockGuard 对象,自动加锁
            std::this_thread::sleep_for(std::chrono::seconds(1));
            std::ofstream outputFile(filename); //覆盖写入
            if (outputFile.is_open()) {
                // 将 JSON 数据写入文件,格式化输出(缩进为4个空格)
                std::cerr  << data.dump(4) << std::endl;
                outputFile << data.dump(4) << std::endl; // 换行
                outputFile.close(); 
                return;
            }
            else
            {
                throw std::ios_base::failure("Failed to open file for writing.");
            }
        }//释放WriteLockGuard
        catch (const std::filesystem::filesystem_error &e)
        {
            const std::string &errorMessage = e.what();
            // debug_log(DLOG_INFO, "File operation failed: %s", errorMessage.c_str());
        }
        catch (const std::ios_base::failure &e)
        {
            const std::string &errorMessage = e.what();
            // debug_log(DLOG_INFO, "File operation failed: %s", errorMessage.c_str());
        }
        catch (const std::exception &e)
        {
            const std::string &errorMessage = e.what();
            // debug_log(DLOG_INFO, "Error: %s", errorMessage.c_str());
        }

        // Introduce a delay before retrying
        std::this_thread::sleep_for(std::chrono::milliseconds(retryDelayMilliseconds));
    }
    // debug_log(DLOG_ERROR, "writeToJsonFile failed, Retry 5 times!!!");
}

/*******************************************************************************
 * 名称: readJsonFromFile
 * 描述: 读取json文件
 * 作者: mkx
 * 参数: void
 * 返回: void
 ******************************************************************************/
json readJsonFromFile(const std::string &filename, int maxRetries, int retryDelayMs)
{
    checkJsonFile(filename);
    FileLock& fileLock = manager.getFileLock(filename);//以文件名为KEY获取对应锁
    for (int attempt = 0; attempt < maxRetries; attempt++)
    {
        try
        {
            ReadLockGuard lock(fileLock); // 创建 ReadLockGuard 对象,自动加锁
            // std::this_thread::sleep_for(std::chrono::seconds(1));
            // 打开文件并直接定位到末尾
            std::ifstream inputFile(filename, std::ios::ate | std::ios::binary); 
            if (inputFile.is_open())
            {
                // 检查文件是否为空(避免创建空文件时要写入空json)
                if (inputFile.tellg() == 0) // 获取文件大小
                {
                    std::cout << "R" <<  json().dump(4) << std::endl; // 使用 dump(4) 格式化输出,缩进为 4 个空格
                    return json(); // 返回一个空的 JSON 对象
                }
                inputFile.seekg(0, std::ios::beg); // 重置文件指针到文件开头
                json loadedData;
                inputFile >> loadedData;
                inputFile.close();
                loadedData["WR"] = "R";

                std::cout  << loadedData.dump(4) << std::endl; // 使用 dump(4) 格式化输出,缩进为 4 个空格
                return loadedData;
            }
            else
            {
                // debug_log(DLOG_INFO, "File %s not found. ", filename.c_str());
                std::cout << "else Loaded JSON " << std::endl;
            }
        }//释放ReadLockGuard
        catch (const std::ios_base::failure &e)
        {
            const std::string &errorMessage = e.what();
            // debug_log(DLOG_INFO, "File operation failed: %s", errorMessage.c_str());
        std::cout << "aaaaaaLoaded JSON " << std::endl;
        }
        catch (const std::exception &e)
        {
            const std::string &errorMessage = e.what();
            // debug_log(DLOG_INFO, "Error: %s", errorMessage.c_str());
        std::cout << errorMessage.c_str() << std::endl;
        }
        // 重试之前等待一段时间
        std::this_thread::sleep_for(std::chrono::milliseconds(retryDelayMs));
    }
    // debug_log(DLOG_ERROR, "readJsonFromFile failed, Retry 5 times!!!");
    // 重试次数超过最大限制,返回空的 JSON 对象表示读取失败
    return json();
}


// 使用fork创建进程并执行任务
void createProcess1(const std::string& proID,const std::string& fileName) {
    pid_t pid = fork();
    
    if (pid == 0) {
        json data = {
            {"name", fileName},
            {"age", proID},
            {"WR", "W"}
        }; 
        // 在子进程中创建线程进行读写操作
        std::thread reader1(readJsonFromFile,fileName,1,100);
        std::thread reader2(readJsonFromFile,fileName,2,100);

        std::thread writer1(writeToJsonFile,fileName,data,3,100);
        std::thread writer2(writeToJsonFile,fileName,data,4,100);

        reader1.join();
        reader2.join();
        writer1.join();
        writer2.join();

        exit(0); // 退出子进程
    } else if (pid > 0) {
        // 父进程
        wait(NULL); // 等待子进程结束
    } else {
        std::cerr << "Fork failed!" << std::endl;
    }
}

int main() {
    std::string fileName1 = "1.txt";
    std::string fileName2 = "2.txt";
    std::string fileName3 = "3.txt";
    std::string fileName4 = "4.txt";
    std::string fileName5 = "5.txt";
    std::string fileName6 = "6.txt";
    std::string fileName7 = "7.txt";
    std::string fileName8 = "8.txt";
    std::string fileName9 = "9.txt";
    std::string fileName0 = "0.txt";
    std::string fileNamea = "a.txt";
    std::string fileNameb = "b.txt";
    std::string fileNamec = "c.txt";
    std::string fileNamed = "d.txt";
    std::string fileNamee = "e.txt";
    std::string fileNamef = "f.txt";
    std::string fileNameg = "g.txt";

        // 创建多个进程进行并行执行
    std::vector<std::thread> threads;
    threads.emplace_back(createProcess1,"P1", fileName1);
    threads.emplace_back(createProcess1,"P2", fileName1);
    threads.emplace_back(createProcess1,"P3", fileName1);
    threads.emplace_back(createProcess1,"P4", fileName1);

    // 等待所有线程完成
    for (auto& t : threads) {
        t.join();
    }
    return 0;
}

http://www.kler.cn/a/501827.html

相关文章:

  • DolphinScheduler自身容错导致的服务器持续崩溃重大问题的排查与解决
  • 【微信小程序】let和const-综合实训
  • 不同音频振幅dBFS计算方法
  • 六十九:基于openssl实战验证RSA
  • 量子计算:从薛定谔的猫到你的生活
  • 《跟我学Spring Boot开发》系列文章索引❤(2025.01.09更新)
  • 【初识扫盲】逆概率加权
  • Windows 10 ARM工控主板连接I2S音频芯片
  • 32_Redis分片集群原理
  • 《零基础Go语言算法实战》【题目 2-26】goroutine 的执行效率问题
  • HDFS 的API的操作
  • 【Rust】函数
  • 【网络协议】EIGRP - 第二部分
  • 使用Deepseek搭建类Cursor编辑器
  • SQL语言的计算机基础
  • LeetCode:216.组合总和III
  • 基于单片机的书写坐姿规范提醒器的设计(论文+源码)
  • 自动化机械臂视觉跟踪和手眼校准
  • Docker Swarm、Kubernetes 和 LVS 的功能对比
  • Go语言如何实现高性能缓存服务
  • 青少年编程与数学 02-006 前端开发框架VUE 24课题、UI表单
  • virtual box虚拟机误删Python3.6后导致UBUNTU18.04开机无UI界面(进不了desktop)的解决方法
  • docker推送本地仓库报错
  • 2025年VGC大众汽车科技社招入职测评综合能力英语口语SHL历年真题汇总、考情分析
  • 嵌入式入门Day42
  • Centos8部署Redis Cluster