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

安全编码课程 实验7 并发

实验项目:C++ 多线程中的数据竞争与同步机制

实验要求:

1. 编写基础代码:模拟账户余额取款

创建一个全局共享变量 int balance = 100,表示初始余额;

创建两个线程 Thread A 和 Thread B,尝试各自取出 100 元;

不使用任何同步机制,观察两个线程是否可能都成功取款(产生负余额);

输出每个线程的操作过程和最终余额。

2. 引入 std::atomic<int> 实现原子性控制

使用 std::atomic<int> 声明余额变量balance;

使用 compare_exchange_weak 实现原子性“读取余额并扣款”的逻辑;

再次运行程序,观察是否仍然出现竞争问题。

3. 使用 std::mutex 锁住临界区

改用普通变量 int balance,但在关键操作处加锁(std::lock_guard<std::mutex> lock(mtx);),确保线程间互斥

再次运行程序,观察是否仍然出现竞争问题。

4. 总结以上三种方式的优缺点

实验步骤、实验结果及结果分析:

1. 编写基础代码:模拟账户余额取款

创建一个全局共享变量 int balance = 100,表示初始余额;

创建两个线程 Thread A 和 Thread B,尝试各自取出 100 元;

不使用任何同步机制,观察两个线程是否可能都成功取款(产生负余额);

输出每个线程的操作过程和最终余额。

创建一个全局共享变量 int balance = 100,表示初始余额;

int balance = 100; // 全局共享变量

创建两个线程 Thread A 和 Thread B,尝试各自取出 100 元;

    std::thread thread_a(withdraw, "Thread A", 100);
    std::thread thread_b(withdraw, "Thread B", 100);

不使用任何同步机制,观察两个线程是否可能都成功取款(产生负余额);

输出每个线程的操作过程和最终余额。

#include <iostream>   
#include <thread>   
#include <chrono>
   
int balance = 100; // 全局共享变量
   
void withdraw(const std::string& thread_name, int amount) {
    if (balance >= amount) {
        std::cout << thread_name << ": 当前余额 " << balance << " 元,尝试取款 " << amount << " 元\n";
        balance -= amount;
        std::cout << thread_name << ": 取款成功,新余额 " << balance << " 元\n";
    } else {
        std::cout << thread_name << ": 余额不足,取款失败\n";
    }   
}
   
int main() {
    std::cout << "初始余额: " << balance << " 元\n";
    
    std::thread thread_a(withdraw, "Thread A", 100);
    std::thread thread_b(withdraw, "Thread B", 100);
    
    thread_a.join();
    thread_b.join();
    
    std::cout << "最终余额: " << balance << " 元\n";
    return 0;   
}

编译多线程程序时链接 pthread 库 :

g++ 1.cpp -o 1 -pthread

竞争条件导致负余额: 

 


 

2. 引入 std::atomic<int> 实现原子性控制

使用 std::atomic<int> 声明余额变量balance;

使用 compare_exchange_weak 实现原子性“读取余额并扣款”的逻辑;

再次运行程序,观察是否仍然出现竞争问题。

std::atomic<int> balance(100); // 原子变量
   
void withdraw(const std::string& thread_name, int amount) {
    int expected = balance.load();
    while (expected >= amount && 
           !balance.compare_exchange_weak(expected, expected - amount)) {
    }
    
    if (expected >= amount) {
        std::cout << thread_name << ": 取款成功,新余额 " << balance.load() << " 元\n";
    } else {
        std::cout << thread_name << ": 余额不足,取款失败\n";
    }   
}

 

3. 使用 std::mutex 锁住临界区

改用普通变量 int balance,但在关键操作处加锁(std::lock_guard<std::mutex> lock(mtx);),确保线程间互斥

再次运行程序,观察是否仍然出现竞争问题。

int balance = 100; // 普通变量   
std::mutex mtx;     // 互斥锁
   
void withdraw(const std::string& thread_name, int amount) {
    std::lock_guard<std::mutex> lock(mtx); // 自动加锁解锁
    
    if (balance >= amount) {
        balance -= amount;
        std::cout << thread_name << ": 取款成功,新余额 " << balance << " 元\n";
    } else {
        std::cout << thread_name << ": 余额不足,取款失败\n";
    }   
}

 

 4. 总结以上三种方式的优缺点

  1. 无同步机制的问题

    • 两个线程可能同时读取到余额为100
    • 都认为可以取款,导致最终余额为-100
    • 这是典型的竞争条件。
  2. atomic解决方案

    • compare_exchange_weak会在原子操作中比较并交换值

    • 如果其他线程修改了值,操作会失败并重试
  3. mutex解决方案

    • 使用锁确保同一时间只有一个线程能访问临界区
    • lock_guard在构造时加锁,析构时自动解锁

相关文章:

  • 如何用服务预约让客单价提升20%?
  • 图像预处理-边缘填充,透视变换和色彩空间基础
  • go中我遇到的问题总结
  • 7.5 使用MobileNet v3进行图像的区分
  • 阿里滑块 231 231纯算 水果滑块 拼图 1688滑块 某宝 大麦滑块 阿里231 验证码
  • 【CHNS】 各个文件,数据信息备注
  • C++ (new和delete运算符,链接库,面向对象与面向过程)
  • SecProxy - 自动化安全协同平台
  • [reinforcement learning] 是什么 | 应用场景 | Andrew Barto and Richard Sutton
  • ros2_01
  • Qt QML - qmldir使用方法详解
  • Leetcode——137 260找出只出现一次的数
  • 【密码学——基础理论与应用】李子臣编著 第六章 祖冲之序列密码 课后习题
  • Trae 下安装 Pylance 插件(仅作为实验,版权由微软所有)
  • 【lerobot】3-开源SO-100 主从臂的舵机位置校正、遥控操作(ubuntu系统)
  • 基于图扩散小波的连接组分析:定位结构-功能映射中的扩散源
  • docker部署GPUStack【Nvidia版本】
  • 【Hot100】239. 滑动窗口最大值
  • Express中间件(Middleware)详解:从零开始掌握(4)
  • 高级java每日一道面试题-2025年4月07日-微服务篇[Nacos篇]-如何监控Nacos的运行状态?
  • 老旧高层遭“连环漏水”,如何携手共治解难题?
  • 李家超:香港特区政府积极推进十五运会各项筹办工作
  • 城事|2小时40分42秒,天工夺冠!全球首个人形机器人半马开跑
  • 《王牌对王牌》确认回归,“奔跑吧”将有主题乐园
  • 为护航企业“出海”,“无问西东·中外商会”海上沙龙举行
  • “不可见社会”:一周城市生活