You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何用C++原子操作解决银行账户多线程临界区重复计息问题?

解决原子操作下重复添加利息的问题

你的问题核心在于计数器递增与触发条件检查的非原子性:多个线程可能同时看到m_iCounter为99,各自执行++m_iCounter后都得到100,进而都进入addInterest函数,导致利息被重复添加。用原子操作解决的关键是让“递增计数器+判断是否触发利息”这一逻辑成为原子不可分割的步骤,或者在addInterest内做二次校验确保只有一个线程执行利息添加。

方案一:从源头阻止多线程进入addInterest(推荐)

利用原子类型的fetch_add方法,它会原子性地将计数器加1并返回递增前的旧值。通过判断旧值+1是否为100的倍数,就能确保只有第一个将计数器推到100倍数的线程触发利息添加:

// 确保m_iCounter是std::atomic<int>类型
void transfer(Account& acc, int amount, string& thread) {
    int old_counter = m_iCounter.fetch_add(1); // 原子递增,返回递增前的值
    withdraw(acc, amount);
    // 只有当递增后的值是100的倍数时,才执行利息添加
    if ((old_counter + 1) % 100 == 0) {
        addInterest(thread);
    }
}

这个方案从根源上避免了多线程同时进入addInterest的问题,效率最高。

方案二:在addInterest内做二次校验(兼容现有transfer逻辑)

如果不想修改transfer的结构,可以在addInterest内部通过一个原子标记,确保同一批次的利息只会被添加一次:

void addInterest(string& thread) {
    // 计算当前应该触发利息的计数器阈值(比如100、200...)
    int current_batch = (m_iCounter.load() / 100) * 100;
    // 用静态原子变量记录已经处理过的最后一个批次
    static std::atomic<int> last_processed_batch = 0;
    
    int expected = last_processed_batch.load();
    // 原子性地尝试更新批次标记,只有第一个线程能成功
    while (current_batch > expected && !last_processed_batch.compare_exchange_weak(expected, current_batch)) {
        // 循环直到获取到最新的last_processed_batch值
    }
    
    // 如果当前批次已经被其他线程处理过,直接返回
    if (current_batch != last_processed_batch) {
        return;
    }
    
    // 以下是原有的利息添加逻辑,确保m_iBalance是std::atomic<float>
    float old = m_iBalance.load();
    const float interest = 0.05f;
    const float amount = old * interest;
    while (!m_iBalance.compare_exchange_weak(old, old + amount)) {
        // 自旋直到CAS成功
    }
    
    // 确保m_iInterest也是原子类型,避免计数错误
    m_iInterest.fetch_add(1);
    cout << thread << " interest : Acc" << name << " " << m_iCounter << endl;
}

额外注意事项

  • 所有共享变量(m_iCounterm_iBalancem_iInterest)都必须声明为对应的原子类型(std::atomic<int>std::atomic<float>),否则仍会存在线程安全问题。
  • std::atomic<float>的CAS操作在部分平台可能存在精度问题,如果对精度要求高,可以考虑将余额转换为整数(比如以分为单位存储),再用std::atomic<long long>处理,避免浮点数的原子操作风险。

内容的提问来源于stack exchange,提问作者Ettur

火山引擎 最新活动