首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >跨进程同步难题?一个通用进程互斥锁的封装与实现

跨进程同步难题?一个通用进程互斥锁的封装与实现

作者头像
开源519
发布2025-08-19 14:34:07
发布2025-08-19 14:34:07
2110
举报
文章被收录于专栏:开源519开源519

脚步不停,终达卓越!更多底层开发技巧,欢迎关注公众号《开源519》

引言

  在多进程开发中,共享资源(如配置文件、设备接口、共享内存块)的竞争访问是常见场景。若不加以控制,轻则导致数据错乱,重则引发进程崩溃。

  传统解决方案中: 信号量(semaphore) 虽能实现跨进程同步,但接口设计繁琐(需手动管理计数);普通线程 mutex 仅能在单进程内生效,无法跨进程协同。更棘手的是,若持有锁的进程意外崩溃,未释放的锁可能导致其他进程永久阻塞——这类 “锁泄露” 问题往往难以排查。

  秉持一贯的优雅编程理念,基于共享内存与 pthread 接口,设计并实现一个开箱即用、兼顾安全与易用的进程间互斥锁 ProcMutex 类,支持跨进程同步与异常安全,并通过 RAII 机制简化锁的生命周期管理。

需求分析

  一个可靠的进程间互斥锁需满足以下核心需求:

  • 跨进程有效性:能在多个独立进程间同步,而非局限于单进程内的线程。
  • 异常容错:当持有锁的进程崩溃时,其他进程能检测并恢复锁状态,避免永久阻塞。
  • 线程安全:同一进程内的多个线程调用时,不会出现状态错乱。
  • 易用性:提供类似普通 mutex 的 Lock()/Unlock() 接口,最好支持自动释放(避免手动解锁遗漏)。
  • 资源自动管理:进程退出时,自动清理共享资源(如共享内存),避免残留。

详细设计

  基于上述需求,ProcMutex 类的设计围绕 “共享内存存状态,pthread 锁做同步,原子变量保安全” 展开,核心代码如下:

核心结构与类定义(ProcMutex.h)

代码语言:javascript
复制
#ifndef __PROC_MUTEX_H__
#define __PROC_MUTEX_H__

#include <mutex>
#include <string>
#include <pthread.h>
#include <atomic>

// 共享内存中的数据结构,存储锁状态与计数
struct SharedData {
    std::atomic<int> refCnt;       // 引用计数(当前持有锁的次数)
    std::atomic<int> waitCnt;      // 等待锁的进程/线程数
    pthread_mutex_t dataMutex;     // 用于保护临界区的核心锁
    pthread_mutex_t waitMutex;     // 用于保护 waitCnt 的辅助锁
};

class ProcMutex {
public:
    explicit ProcMutex(const std::string& mutexName);  // 构造时初始化共享资源
    ~ProcMutex();                                       // 析构时清理资源

    void Lock();   // 加锁
    void Unlock(); // 解锁

private:
    void Init();       // 初始化共享内存与锁
    void DeInit();     // 销毁共享资源
    void AddWait();    // 增加等待计数
    void DelWait();    // 减少等待计数

private:
    int mShmFd;               // 共享内存文件描述符
    std::string mMutexName;   // 锁名称(用于标识共享内存)
    SharedData* mSharedData;  // 共享内存映射的指针
};

// RAII 封装,自动加解锁
class ProcLockGuard {
public:
    explicit ProcLockGuard(ProcMutex& pMutex, std::mutex& tMutex);
    ~ProcLockGuard();

private:
    std::mutex& mTMutex;  // 线程内 mutex(防止同一进程内线程竞争)
    ProcMutex& mPMutex;   // 进程间 mutex
};

#endif // __PROC_MUTEX_H__

实现细节(ProcMutex.cpp)

  核心逻辑集中在共享内存初始化、锁状态管理与异常处理,关键代码解析如下:

    1. 初始化:共享内存与跨进程锁 ① 通过共享内存和pthread_mutexattr_setpshared 将互斥锁允许进程共享。 ② 通过pthread_mutexattr_setrobust 开启健壮模式,持有者崩溃时,其他进程能检测到并恢复。
代码语言:javascript
复制
void ProcMutex::Init() {
    // 1. 创建/打开共享内存(以 mutex 名称为标识)
    mShmFd = shm_open(mMutexName.c_str(), O_RDWR | O_CREAT, 0744);
    if (mShmFd == -1) {
        SPR_LOGE("shm_open failed! (%s)\n", strerror(errno));
        return;
    }
    // 2. 设置共享内存大小为 SharedData 结构大小
    if (ftruncate(mShmFd, sizeof(SharedData)) == -1) {
        SPR_LOGE("ftruncate failed! (%s)\n", strerror(errno));
        close(mShmFd);
        return;
    }

    // 3. 映射共享内存到进程地址空间
    mSharedData = reinterpret_cast<SharedData*>(mmap(NULL, sizeof(SharedData),
                                        PROT_READ | PROT_WRITE, MAP_SHARED, mShmFd, 0));
    if (mSharedData == MAP_FAILED) {
        SPR_LOGE("mmap failed! (%s)\n", strerror(errno));
        close(mShmFd);
        return;
    }

    // 4. 初始化 mutex 属性(关键:设置为跨进程共享+健壮模式)
    pthread_mutexattr_t mutexAttr;
    pthread_mutexattr_init(&mutexAttr);
    // 允许 mutex 在进程间共享
    pthread_mutexattr_setpshared(&mutexAttr, PTHREAD_PROCESS_SHARED);
    // 开启健壮模式:当持有者崩溃时,其他进程能检测到并恢复
    pthread_mutexattr_setrobust(&mutexAttr, PTHREAD_MUTEX_ROBUST);

    // 5. 首次初始化共享内存中的锁(refCnt 为 0 表示未初始化)
    if (mSharedData->refCnt == 0) {
        pthread_mutex_init(&mSharedData->dataMutex, &mutexAttr);
        pthread_mutex_init(&mSharedData->waitMutex, &mutexAttr);
        mSharedData->refCnt = 0;
        mSharedData->waitCnt = 0;
    }

    pthread_mutexattr_destroy(&mutexAttr);
}
    1. 加锁与异常处理
代码语言:javascript
复制
void ProcMutex::Lock() {
    if (!mSharedData) return;

    // 先尝试加锁,避免直接阻塞
    int rc = pthread_mutex_trylock(&mSharedData->dataMutex);
    if (rc != 0) {
        AddWait();  // 加锁失败,增加等待计数
        rc = pthread_mutex_lock(&mSharedData->dataMutex);

        // 若持有者崩溃,尝试恢复锁状态(健壮模式的核心作用)
        if (rc == EOWNERDEAD) {
            SPR_LOGW("Mutex owner died, trying to recover\n");
            if (pthread_mutex_consistent(&mSharedData->dataMutex) != 0) {
                SPR_LOGE("Failed to make mutex consistent\n");
                DelWait();
                return;
            }
            rc = 0;
        }

        if (rc != 0) {
            SPR_LOGE("pthread_mutex_lock failed: %s\n", strerror(rc));
            DelWait();
            return;
        }
        DelWait();  // 加锁成功,减少等待计数
    }

    // 原子增加引用计数(线程安全)
    mSharedData->refCnt.fetch_add(1, std::memory_order_relaxed);
}
    1. RAII 自动管理:ProcLockGuard
代码语言:javascript
复制
ProcLockGuard::ProcLockGuard(ProcMutex& pMutex, std::mutex& tMutex)
    : mTMutex(tMutex), mPMutex(pMutex) {
    mTMutex.lock();    // 先锁线程内 mutex(防止同一进程内多线程竞争)
    mPMutex.Lock();    // 再锁进程间 mutex
}

ProcLockGuard::~ProcLockGuard() {
    mPMutex.Unlock();  // 先释放进程间锁
    mTMutex.unlock();  // 再释放线程内锁
}

实例使用

持锁进程崩溃演示

  • 示例代码 验证当持锁进程崩溃时,其他进程能否从阻塞状态恢复并成功获取锁。
代码语言:javascript
复制
const std::string DEMO_SHARED_MUTEX = "demo_shared_mutex";

void childProcessWork(int processId, bool isCrashProcess)
{
    std::mutex threadMutex;
    ProcMutex procMutex(DEMO_SHARED_MUTEX);
    SPR_LOG("进程 %d: 启动,准备竞争锁...\n", processId);

    // 循环尝试访问共享资源
    for (int i = 0; i < 3; ++i) {
        ProcLockGuard lockGuard(procMutex, threadMutex);
        SPR_LOG("进程 %d: 成功获取锁,正在访问资源(第%d次)\n", processId, i+1);
        sleep(1);

        // 特定进程在持有锁时异常退出(模拟崩溃)
        if (isCrashProcess && i == 1) {
            SPR_LOG("进程 %d: 即将异常退出(持有锁状态)!\n", processId);
            std::abort();
        }

        SPR_LOG("进程 %d: 释放锁,等待下一次竞争...\n", processId);
    }

    SPR_LOG("进程 %d: 正常退出\n", processId);
}

int main()
{
    constint NUM_PROCESSES     = 3;  // 总进程数
    constint CRASH_PROCESS_ID  = 1;  // 指定崩溃的进程ID

    SPR_LOG("=== 跨进程互斥锁演示程序启动 ===       \n"
            "演示内容:                              \n"
            "1. %d个进程竞争同一个共享资源          \n"
            "2. 进程 %d: 在持有锁时异常退出         \n"
            "3. 验证其他进程能否检测并恢复锁状态    \n\n", NUM_PROCESSES, CRASH_PROCESS_ID);

    srand(time(nullptr));
    std::vector<pid_t> childPids;

    // 创建多个子进程
    for (int i = 0; i < NUM_PROCESSES; ++i) {
        pid_t pid = fork();
        if (pid < 0) {
            SPR_LOG("fork失败: %s\n", strerror(errno));
            return EXIT_FAILURE;
        } elseif (pid == 0) {
            // 子进程逻辑:第 CRASH_PROCESS_ID 个进程会崩溃
            childProcessWork(i + 1, (i + 1 == CRASH_PROCESS_ID));
            return EXIT_SUCCESS;
        } else {
            childPids.push_back(pid);
            usleep(50000);  // 错开进程启动时间
        }
    }

    // 父进程等待所有子进程结束
    int status;
    for (pid_t pid : childPids) {
        waitpid(pid, &status, 0);
        if (WIFEXITED(status)) {
            SPR_LOG("父进程:子进程 %d: 正常退出,退出码 %d\n", pid, WEXITSTATUS(status));
        } elseif (WIFSIGNALED(status)) {
            SPR_LOG("父进程:子进程 %d: 异常退出,信号 %d\n", pid, WTERMSIG(status));
        }
    }

    SPR_LOG("\n=== 演示结束 ===\n");
    return EXIT_SUCCESS;
}
  • 效果
代码语言:javascript
复制
$ ./01_proc_guard
=== 跨进程互斥锁演示程序启动 ===
演示内容:
1. 3个进程竞争同一个共享资源
2. 进程 1: 在持有锁时异常退出
3. 验证其他进程能否检测并恢复锁状态

进程 1: 启动,准备竞争锁...
进程 1: 成功获取锁,正在访问资源(第1次)
进程 2: 启动,准备竞争锁...
进程 3: 启动,准备竞争锁...
进程 1: 释放锁,等待下一次竞争...
进程 1: 成功获取锁,正在访问资源(第2次)
进程 1: 即将异常退出(持有锁状态)!
 174 ProcMutex W: Mutex owner died, trying to recover
进程 3: 成功获取锁,正在访问资源(第1次)
父进程:子进程 15076: 异常退出,信号 6
进程 3: 释放锁,等待下一次竞争...
进程 3: 成功获取锁,正在访问资源(第2次)
进程 3: 释放锁,等待下一次竞争...
进程 3: 成功获取锁,正在访问资源(第3次)
进程 3: 释放锁,等待下一次竞争...
进程 3: 正常退出
进程 2: 成功获取锁,正在访问资源(第1次)
进程 2: 释放锁,等待下一次竞争...
进程 2: 成功获取锁,正在访问资源(第2次)
进程 2: 释放锁,等待下一次竞争...
进程 2: 成功获取锁,正在访问资源(第3次)
进程 2: 释放锁,等待下一次竞争...
进程 2: 正常退出
父进程:子进程 15077: 正常退出,退出码 0
父进程:子进程 15078: 正常退出,退出码 0

=== 演示结束 ===

通过演示能够发现进程1在持锁状态崩溃时,进程2进程3能够从阻塞状态恢复并正常获取锁。

临界区保护

主要验证多进程对临界区竞争的保护效果,测试也能够正常通过。由于篇幅过长,不再赘述,可在文末获取源码。

总结

  • ProcMutex 通过共享内存+健壮 mutex 解决了跨进程同步问题,尤其对“进程崩溃导致锁泄露”的场景做了容错处理,提升了系统稳定性。
  • 接口与普通 mutex 一致,ProcLockGuard 实现 RAII 自动管理,降低了手动加解锁的出错风险。
  • 除此之外,还可进一步增加超时加锁(TimedLock)、锁状态查询等接口,后续再增加。

最后

用心感悟,认真记录,写好每一篇文章,分享每一框干货。

更多文章内容包括但不限于C/C++、Linux、开发常用神器等,可进入“开源519公众号”聊天界面输入“文章目录” 或者 菜单栏选择“文章目录”查看。公众号后台聊天框输入本文标题,在线查看源码。 在聊天框输入“开源519资料” 获取Linux C/C++ 学习资料书籍

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-08-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 开源519 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 引言
  • 需求分析
  • 详细设计
    • 核心结构与类定义(ProcMutex.h)
    • 实现细节(ProcMutex.cpp)
  • 实例使用
    • 持锁进程崩溃演示
    • 临界区保护
  • 总结
  • 最后
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档