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

CPP多线程2:多线程竞争与死锁问题

在多线程编程中,多个线程协同工作能显著提升程序效率,但当它们需要共享和操作同一资源时,潜在的问题也随之而来;线程间的执行顺序不确定性可能导致资源竞争,可能引发死锁,让程序陷入停滞。

多线程竞争问题示例

我们现在已经知道如何在c++11中创建线程,那么如果多个线程需要操作同一个变量呢?

#include <iostream>
#include <thread>
using namespace std;
int n = 0;
void count10000() {for (int i = 1; i <= 10000; i++)n++;
}
int main() {thread th[100];for (thread &x : th)x = thread(count10000);for (thread &x : th)x.join();cout << n << endl;return 0;
}

可能的两次输出分别是:

991164
996417

我们的输出结果应该是1000000,可是为什么实际输出结果比1000000小呢?

在多线程的执行顺序——同时进行、无次序,所以这样就会导致一个问题:多个线程进行时,如果它们同时操作同一个变量,那么肯定会出错。为了应对这种情况,c++11中出现了std::atomic和std::mutex。

std::mutex

std::mutex是 C++11 中最基本的互斥量,一个线程将mutex锁住时,其它的线程就不能操作mutex,直到这个线程将mutex解锁。根据这个特性,我们可以修改一下上一个例子中的代码:

#include <iostream>
#include <thread>
#include <mutex>
using namespace std;int n=0;
mutex mtx;
void count10000(){for(auto i=0;i<10000;i++){mtx.lock();n++;mtx.unlock();}
}
int main(){thread th[100];for (thread &x : th)x = thread(count10000);for (thread &x : th)x.join();cout<<n<<endl;return 0;
}

mutex的常用成员函数
在这里插入图片描述

std::lock_gard

使用mutex需要上锁解锁,但有时由于程序员忘记或者其他奇怪问题时,lock_gard可以自动解锁。其原理大概是构造时自动上锁,析构时自动解锁。示例如下:

#include <iostream>
#include <thread>
#include <mutex>
using namespace std;int n=0;
mutex mtx;
void count100000(){for(auto i=0;i<100000;i++){lock_guard<mutex> lock1(mtx);n++;n--;}
}
int main(){thread th[10];for(int i=0;i<10;i++){th[i]=thread(count100000);}for(int i=0;i<10;i++){th[i].join();}cout<<n<<endl;return 0;
}

std::atomic

mutex很好地解决了多线程资源争抢的问题,但它也有缺点:太……慢……了……

比如前面我们定义了100个thread,每个thread要循环10000次,每次循环都要加锁、解锁,这样固然会浪费很多的时间,那么该怎么办呢?接下来就是atomic大展拳脚的时间了。

#include <iostream>
#include <atomic>
#include <thread>
using namespace std;atomic<int> n{0};// 列表初始化void count10000(){for(int i=0;i<10000;i++)n++;
}int main(){thread th[10];for(thread& x:th)x=thread(count10000);for(auto& x:th)x.join();cout<<n<<endl;return 0;
}

可以看到,我们只是改动了n的类型(int->std::atomic_int),其他的地方一点没动,输出却正常了。

atomic,本意为原子,可解释为:

原子操作是最小的且不可并行化的操作。

atomic常用成员函数
在这里插入图片描述

死锁问题

在多个线程中由于上锁顺序问题可能导致线程卡死,如下:

#include <iostream>
#include <thread>
#include <mutex>
using namespace std;int n=0;
mutex mtx1;
mutex mtx2;
void count100000(){for(auto i=0;i<100000;i++){lock_guard<mutex> lock1(mtx1);lock_guard<mutex> lock2(mtx2);n++;n--;}
}
void count200000(){for(auto i=0;i<200000;i++){lock_guard<mutex> lock2(mtx2);lock_guard<mutex> lock1(mtx1);n++;n--;}
}
int main(){thread th[10];for(int i=0;i<10;i++){if(i%2==0)th[i]=thread(count100000);elseth[i]=thread(count200000);}for(int i=0;i<10;i++){th[i].join();}cout<<n<<endl;return 0;
}

这是因为在一个线程count100000中mtx1上锁后,另一个线程count200000也正好将mtx2上锁,于是这两个线程没办法获得另一个mutex,这就是死锁问题。

解决方法就是保持一样的上锁顺序,于是当一个线程A抢到第一个mutex时,其他线程无法再获得mutex,即只能线程A按着顺序处理完所有事物。示例如下:

#include <iostream>
#include <thread>
#include <mutex>
using namespace std;int n=0;
mutex mtx1;
mutex mtx2;
void count100000(){for(auto i=0;i<100000;i++){lock_guard<mutex> lock1(mtx1);lock_guard<mutex> lock2(mtx2);n++;n--;}
}
void count200000(){for(auto i=0;i<200000;i++){lock_guard<mutex> lock1(mtx1);lock_guard<mutex> lock2(mtx2);n++;n--;}
}
int main(){thread th[10];for(int i=0;i<10;i++){if(i%2==0)th[i]=thread(count100000);elseth[i]=thread(count200000);}for(int i=0;i<10;i++){th[i].join();}cout<<n<<endl;return 0;
}
http://www.dtcms.com/a/334311.html

相关文章:

  • 企业级Java项目金融应用领域——银行系统
  • C#WPF实战出真汁09--【消费开单】--选择菜品
  • 驱动开发系列63 - 配置 nvidia 的 open-gpu-kernel-modules 调试环境
  • AI重构文化基因:从“工具革命”到“生态觉醒”的裂变之路
  • 【101页PPT】芯片半导体企业数字化项目方案汇报(附下载方式)
  • 在鸿蒙应用中快速接入地图功能:从配置到实战案例全解析
  • Nginx域名和IP兼容双方的API地址
  • GaussDB 数据库架构师修炼(十三)安全管理(3)-数据库审计
  • 使用npm/pnpm自身安装指定版本的pnpm
  • JavaWeb开发_Day14
  • 如何在 Ubuntu 24.04 Server 或 Desktop 上安装 XFCE
  • 我的世界Java版1.21.4的Fabric模组开发教程(十八)自定义传送门
  • 边缘计算及其特点
  • 学习日志35 python
  • Python Day30 CSS 定位与弹性盒子详解
  • CodeBuddy IDE深度体验:AI驱动的全栈开发新时代
  • 缓存一致性总线协议(Cache Coherence Protocols)的发展过程
  • LangChain4j:基于 SSE 与 Flux 的 AI 流式对话实现方案
  • Honor of Kings 100star (S40) 2025.08.16
  • 11-verilog的RTC驱动代码
  • 10-verilog的EEPROM驱动-单字节读写
  • OpenCV安装及配置
  • 机器学习核心概念精要:从定义到评估
  • 从频繁告警到平稳发布:服务冷启动 CPU 风暴优化实践222
  • 利用 Java 爬虫按图搜索淘宝商品(拍立淘)实战指南
  • AirReceiverLite:轻松实现手机隔空投屏
  • [typescript] interface和type有什么关系?
  • Spark 数据分发性能深度剖析:mapPartitions vs. UDF – 你该选择哪一个?
  • 矩阵链相乘的最少乘法次数(动态规划解法)
  • KVM虚拟化技术解析:从企业应用到个人创新的开源力量