在一次实际开发中遇到程序崩溃问题,代码demo如下:

#include <iostream>
#include <map>
#include <thread>
#include <mutex>
#include <atomic>
using namespace std;//网络回调类
class NetworkCB
{
public:virtual void OnRecv(const char* pData) = 0;
};//客户端类
class Client
{
public:void Connect(NetworkCB* p){//模拟网络接收thread([ = ](){while(true){p->OnRecv("data");this_thread::sleep_for(chrono::milliseconds(1000));}}).detach();}
};//直播类
class Live: public NetworkCB
{
public:void OnRecv(const char* pData){cout << __FUNCTION__ << endl;}void Open(){m_Client.Connect(this);}private:Client m_Client;
};//视频管理类
class VideoManager
{
public://打开视频int OpenVideo(){++m_nVideoId;shared_ptr<Live> pLive = make_shared<Live>();pLive->Open();lock_guard<mutex> mapLock(m_mapMutex);m_mapVideo[m_nVideoId] = pLive;return m_nVideoId;}private://视频索引atomic<int>                m_nVideoId = 0;//map互斥量mutex                      m_mapMutex;//保存视频的mapmap<int, shared_ptr<Live>> m_mapVideo;
};int main()
{VideoManager oVideoManager;//模拟100个并发for(int i = 0; i < 100; ++i){thread([&](){oVideoManager.OpenVideo();}).detach();}getchar();return 0;
}

调试运行程序崩溃:

这个问题我看了一上午找不到原因。

拉来同组的技术大佬老刘一起来看,

老刘右手捋了捋胡子说:“这个VideoManager::OpenVideo函数里面,m_nVideoId是原子类型,m_mapVideo加了锁,看着好像没问题,多半是你这个锁没加好,要不你在最前面加一个大锁试试”

于是我照做试了试,修改代码如下:

//视频管理类
class VideoManager
{
public://打开视频int OpenVideo(){lock_guard<mutex> VideoIdLock(m_VideoIdMutex);++m_nVideoId;shared_ptr<Live> pLive = make_shared<Live>();pLive->Open();lock_guard<mutex> mapLock(m_mapMutex);m_mapVideo[m_nVideoId] = pLive;return m_nVideoId;}private://VideoId互斥量mutex                      m_VideoIdMutex;//视频索引int                        m_nVideoId = 0;//map互斥量mutex                      m_mapMutex;//保存视频的mapmap<int, shared_ptr<Live>> m_mapVideo;
};

我在VideoManager::OpenVideo函数开头加了一个锁m_nVideoId的大锁,m_nVideoId改为非原子类型,果然问题解决了,不崩了,不愧是老刘啊。

突然,小组长把脸凑了过来问到:“你们两个在看啥呢?”

描述了一通后,小组长说:“你这肯定不行啊,m_nVideoId的锁范围过大了,实际开发中pLive->Open()的耗时可能需要几百毫秒,这个锁的范围这么大,性能肯定要受影响啊”

我和老刘面面相觑,一时说不出话来。

小组长一脸不屑的说:“哎,让开,我来给你们改改”。

一顿操作,修改代码如下:

//视频管理类
class VideoManager
{
public://打开视频int OpenVideo(){m_VideoIdMutex.lock();int nTempVideoId = ++m_nVideoId;;m_VideoIdMutex.unlock();shared_ptr<Live> pLive = make_shared<Live>();pLive->Open();lock_guard<mutex> mapLock(m_mapMutex);m_mapVideo[nTempVideoId] = pLive;return nTempVideoId;}private://VideoId互斥量mutex                      m_VideoIdMutex;//视频索引int                        m_nVideoId = 0;//map互斥量mutex                      m_mapMutex;//保存视频的mapmap<int, shared_ptr<Live>> m_mapVideo;
};

突然老刘拍了一下桌子,大喊一声:“妙啊,妙啊”。

此时还在蒙蔽状态的我,看着代码心里想着:哪里妙了?

小组长看着我二人,开始了教学模式,说到:“首先分析崩溃原因,问题在VideoManager::OpenVideo这个函数,m_nVideoId是原子类型,m_mapVideo加了锁,但是++m_nVideoId到m_mapVideo[m_nVideoId] = pLive不是原子操作,同一个线程两次m_nVideoId可能不等,然后两个线程相同的m_nVideoId加入了map,但是map的key是唯一的,导致先加进map那个m_nVideoId的智能指针指向的对象释放,然而网络这边还在接收数据处理,就出现访问非法内存导致崩溃。其次,我们知道一个函数多线程运行的时候,只有共享的数据有线程安全问题,而其他的,比如局部变量就没有线程安全问题。VideoManager::OpenVideo函数两次访问m_nVideoId,一次是写,一次是读,中间又有耗时的pLive->Open(),于是想到用局部变量来保存m_nVideoId读这次操作,这样,写、读m_nVideoId在一起,而且在锁里面。避免了加锁范围过大带来的性能问题,同时也解决了程序崩溃问题“。

这个问题我们从头到尾用伪代码再分析一遍,看透问题的本质。

问题简化如下:

void fun()
{加锁写共享数据解锁耗时操作加锁读共享数据解锁
}

问题在于一个线程两次访问的共享数据不一致,写共享数据和读共享数据不是一个原子操作。

修改为如下:

void fun()
{加锁写共享数据耗时操作读共享数据解锁
}

写共享数据和读共享数据现在是一个原子操作了,但是由于中间有耗时操作,导致性能低下。

所以,假如写共享数据、读共享数据中间没有耗时操作就好了

修改为如下:

void fun()
{加锁写共享数据局部变量读共享数据解锁耗时操作读局部变量
}

巧妙的利用局部变量保存了读共享数据,使写、读共享数据在一起,中间没有了耗时操作。

问题终于解决!

记一次多线程问题引发的崩溃相关推荐

  1. 记一次replace into引发的死锁问题

    记一次replace into引发的死锁问题 需求是使用kafka监听历史表的变动,写入到新表之中.写数据的核心代码用到了mysql 的 replace into public hanlde(Debe ...

  2. 日常学习——记使用POI多线程写Excel数据(续)

    前几天看到有人问到我之前写的一篇博客的中的内容:记使用POI多线程写Excel数据的过程和收获,存在部分疑问 原本那边博客只是一时兴起写的东西,有头没尾,其实到最后只是知道错了,但是不知道哪里错了. ...

  3. c语言多线程造成的崩溃,C++多线程析构函数引起程序崩溃解析.pdf

    C多线程析构函数引起程序崩溃解析 当析构函数遇到多线程 ── C++ 中线程安全的对象回调 陈硕 (giantchen_AT_gmail) B/Solstice 摘要 编写线程安全的类不是难事,用同步 ...

  4. 记一则神秘JDK版本引发的hadoop集群慢性崩溃”血案“

    一.症状表现 前些时间公司在外省机房部署了一套新hadoop集群,所有机子都装的是centos,跑了一个礼拜莫名其妙的出现了计算节点的心跳间隔变得越来越大,最终导致计算节点挂掉,遇到问题第一时间就是看 ...

  5. Thinking in java 多线程导致数组越界崩溃的BUG

    作者:wenyinfeng 转载时,请注明原文出处,谢谢! //: concurrency/SynchronizationComparisons.java // Comparing the perfo ...

  6. c++ map 多线程同时更新值 崩溃_深入理解并发安全的 sync.Map

    golang中内置了map关键字,但是它是非线程安全的.从go 1.9开始,标准库加入了sync.Map,提供用于并发安全的map. 普通map的并发问题 map的并发读写代码 func main() ...

  7. com.haodf.android,有坑!Android新版QQ获取packageInfo引发异常崩溃

    起因 最近从错误日志中检查到一个异常崩溃: java.lang.RuntimeException: Package manager has died at android.app.ActivityTh ...

  8. 2 str转byte失败_android 4.2的多线程库加载崩溃问题

    研究线上崩溃时,发现在Android 4.2的设备上,经常会遇到调用System.load()加载库的时候,崩溃在libc.so中. 对应的java调用栈: #1: java.lang.Runtime ...

  9. winform 让他间隔一段时间 执行事件 且只执行一次_记一次golang定时器引发的诡异错误...

    作为一只在9127工作制下摸鱼的程序猿,周六自然是愉快的加班了.一早上除了一位新同学在我们的"敏捷迭代"下错删了接口之外没什么大事. 临近中午,突然隔壁组大佬找到我,表示有个go语 ...

最新文章

  1. ap忘记管理ip地址怎么办_什么是无线AP?胖瘦AP如何区分?
  2. php 固定人数拼手气_独立统计在线人数和访问数代码分享(php)
  3. 【算法】 - 动态规划 + 位运算
  4. 圆网印花色浆未干引起的印花疵病
  5. Vim查找与替换命令大全,功能完爆IDE!
  6. python中mod运算符_Python—运算符模块,pythonoperator
  7. 【Driver】协作安装程序
  8. 中国地图和地方特点介绍
  9. 基于MATLAB的数字信号处理系统的GUI界面实现
  10. 机器学习中特征空间、欧式空间、希尔伯特空间以及特征空间
  11. SONY索尼摄像机Z280断电KLV.RSV.MXF视频打不开数据恢复成功
  12. Erlang开源20周年:这门编程语言见证了互联网的技术成长
  13. 网易乐商北京(电面一)
  14. ARM服务器搭建 我的世界(MC) 1.18.2 版私服教程
  15. 电视机当计算机屏幕,电视机能当显示器吗?电视机是更大更便宜 但真的做不了显示器...
  16. libreCAD源码阅读笔记3
  17. 一个业务型算法工程师的技能清单
  18. Mongodb本机部署副本集
  19. 需求的获取:需求调研中的5W+1H定律
  20. Android 上网本上使用3G上网卡的可行性

热门文章

  1. 在算法研究过程中如何进行算法创新
  2. python 批量下载小说
  3. oracle 分区表空间迁移,oracle分区表的迁移
  4. Neutrino追问AMA第16期|everiToken程希冀:通过安全合约技术让用户一键发通证
  5. android进度条脚本,GEE引擎自定义进度条和自定义属性示例脚本..
  6. ibm tivoli_解决Tivoli Directory Server同步问题的方法
  7. 紫林U盘解锁精灵V1.0.1
  8. 量子力学奇妙之旅-微扰论和变分法
  9. Win7下合并U盘分区
  10. 动力环境监控系统动环系统