You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在C++中实现多线程访问资源池双资源的同步管理

多线程资源同步问题分析与优化方案

问题背景

模拟6个线程访问共享资源池的场景:资源池包含6个初始可用的资源,每个线程需要同时占用两个资源,使用完成后释放。要求避免竞态条件,同时保证不共享资源的线程可并发执行(如T1和T3)、共享资源的线程互斥执行(如T1和T2),拒绝全局锁导致的效率低下问题。

线程与资源的对应关系:

  • T1(coupe):res[0]、res[1]
  • T2(epee):res[1]、res[2]
  • T3(chandelier):res[2]、res[3]
  • T4(bague):res[3]、res[4]
  • T5(table):res[4]、res[5]
  • T6(porte):res[0]、res[5]

现有方案的错误分析

你提出的「每个资源对应一个信号量」的思路方向正确,但代码存在以下关键问题:

  1. 信号量初始化错误:std::binary_semaphore sem0{0}将初始值设为0,意味着资源一开始不可用,但实际资源初始状态为可用,正确初始值应为1。
  2. 竞态条件未解决:检查el1数组状态与获取信号量的操作不是原子的。例如线程A检查el1[0]为1后,线程B可能抢先占用该资源并修改el1[0]为0,此时线程A再执行el1[0] = 0会导致状态错误。
  3. 信号量逻辑错误:仅当el1显示资源被占用时才调用acquire,但初始信号量为0,第一次执行会直接阻塞线程,无法正常获取资源。

修正后的单资源信号量实现方案

以下是正确的实现,核心思路是用二元信号量直接管理每个资源的状态,去掉冗余的bool数组,确保资源的获取和释放是原子操作:

#include <iostream>
#include <string>
#include <thread>
#include <semaphore>
#include <chrono>
#include <syncstream>
#include <algorithm> // 用于std::min/std::max

const int64_t tempsCreationObjetsMS = 3000;

// 每个资源对应一个二元信号量,初始值1表示资源可用
std::binary_semaphore sem_res[6] = {
    std::binary_semaphore{1},
    std::binary_semaphore{1},
    std::binary_semaphore{1},
    std::binary_semaphore{1},
    std::binary_semaphore{1},
    std::binary_semaphore{1}
};

void creer_thread(const std::string& nom, int res_a, int res_b) {
    std::osyncstream(std::cout) << "Je suis le thread " << nom << std::endl;

    // 按资源编号从小到大获取,避免死锁
    int first_res = std::min(res_a, res_b);
    int second_res = std::max(res_a, res_b);

    // 获取第一个资源
    sem_res[first_res].acquire();
    std::osyncstream(std::cout) << nom << " a acquis la resource " << first_res << std::endl;
    
    // 获取第二个资源
    sem_res[second_res].acquire();
    std::osyncstream(std::cout) << nom << " a acquis la resource " << second_res << std::endl;

    // 使用资源
    std::osyncstream(std::cout) << nom << " en creation." << std::endl;
    std::this_thread::sleep_for(std::chrono::milliseconds(tempsCreationObjetsMS));
    std::osyncstream(std::cout) << nom << " cree." << std::endl;

    // 释放资源(顺序不影响)
    sem_res[second_res].release();
    sem_res[first_res].release();
    std::osyncstream(std::cout) << nom << " a libere les ressources " << first_res << " et " << second_res << std::endl;
}

int main(void) {
    std::thread th1(creer_thread, "coupe", 0, 1);
    std::thread th2(creer_thread, "epee", 1, 2);
    std::thread th3(creer_thread, "chandelier", 2, 3);
    std::thread th4(creer_thread, "bague", 3, 4);
    std::thread th5(creer_thread, "table", 4, 5);
    std::thread th6(creer_thread, "porte", 0, 5);

    th1.join();
    th2.join();
    th3.join();
    th4.join();
    th5.join();
    th6.join();

    return 0;
}

方案优势

  • 无竞态条件:信号量的acquire和release是原子操作,确保资源不会被多个线程同时占用。
  • 高效并发:不共享资源的线程(如T1和T3)可以同时执行,不会被全局锁阻塞。
  • 避免死锁:通过按资源编号从小到大的顺序获取资源,破坏了死锁的"环形等待"条件,彻底避免死锁风险。
  • 代码简洁:去掉了冗余的bool数组,直接用信号量管理资源状态,逻辑更清晰。

替代方案:使用std::mutex实现

如果更倾向于用互斥锁,也可以为每个资源分配一个std::mutex,实现逻辑与信号量方案一致:

#include <iostream>
#include <string>
#include <thread>
#include <mutex>
#include <chrono>
#include <syncstream>
#include <algorithm>

const int64_t tempsCreationObjetsMS = 3000;

std::mutex res_mutex[6];

void creer_thread(const std::string& nom, int res_a, int res_b) {
    std::osyncstream(std::cout) << "Je suis le thread " << nom << std::endl;

    int first_res = std::min(res_a, res_b);
    int second_res = std::max(res_a, res_b);

    // 使用lock_guard实现RAII,避免异常导致锁无法释放
    std::lock_guard<std::mutex> lock1(res_mutex[first_res]);
    std::osyncstream(std::cout) << nom << " a bloque la resource " << first_res << std::endl;
    
    std::lock_guard<std::mutex> lock2(res_mutex[second_res]);
    std::osyncstream(std::cout) << nom << " a bloque la resource " << second_res << std::endl;

    std::osyncstream(std::cout) << nom << " en creation." << std::endl;
    std::this_thread::sleep_for(std::chrono::milliseconds(tempsCreationObjetsMS));
    std::osyncstream(std::cout) << nom << " cree." << std::endl;

    // lock_guard会自动释放锁,无需手动unlock
    std::osyncstream(std::cout) << nom << " a debloque les ressources " << first_res << " et " << second_res << std::endl;
}

// main函数与信号量方案一致,此处省略

注意事项

使用std::mutex时,优先搭配std::lock_guard或std::unique_lock实现RAII,即使线程执行过程中抛出异常,锁也会在对象销毁时自动释放,保证资源状态正确。

内容的提问来源于stack exchange,提问作者Nicolas Cinq-Mars

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 12:13:09