【VS开发】C++线程安全
我们是多么渴望各种C++类都是多线程安全的,然而一旦涉及到对象间的交互,这样的渴望可能就只能是奢望了。下面,我们以设计一个双向链结点为例,看看要使其多线程安全将会带来一些什么问题。
class DoublyLinedNode{
DoublyLinedNode* pPrevNode_;
DoublyLinedNode* pNextNode_;
public:
DoublyLinedNode() : pPrevNode_(0), pNextNode_(0){}
virtual ~DoublyLinedNode();
public:
const DoublyLinedNode* GetPrevNode() const{return pPrevNode_;}
const DoublyLinedNode* GetNextNode() const{return pNextNode_;}
public:
void InsertPrevNode(DoublyLinedNode* p);
void InsertNextNode(DoublyLinedNode* p);
void Break();
};
这是一个简单的双向链结点类,我们就讨论讨论其Break接口,这个接口的作用是使结点从其所在的链中断开,如图:
它的实现可能是这样的:
void DoublyLinedNode::Break()
{
if (pPrevNode_)
{
pPrevNode_->pNextNode_ = pNextNode_;
}
if (pNextNode_)
{
pNextNode_->pPrevNode_ = pPrevNode_;
}
pPrevNode_ = 0;
pNextNode_ = 0;
}
这个实现是单线程模式的,没有多线程安全性。
第一次尝试:
void DoublyLinedNode::Break()
{
Lock();
if (pPrevNode_)
{
pPrevNode_->pNextNode_ = pNextNode_;
}
if (pNextNode_)
{
pNextNode_->pPrevNode_ = pPrevNode_;
}
pPrevNode_ = 0;
pNextNode_ = 0;
UnLock();
}
我们第一次尝试将这个接口的代码用多线程锁锁住了,然而问题很明显:
if (pPrevNode_)
{
pPrevNode_->pNextNode_ = pNextNode_;
}
if (pNextNode_)
{
pNextNode_->pPrevNode_ = pPrevNode_;
}
我们这两个对前向和后向结点的操作是修改另外两个对象的内部状态,多线程中,可能在此时正好有其他线程在对这两个对象进行操作(访问),或许程序就会因此而崩溃。
第二次尝试:
void DoublyLinedNode::Break()
{
Lock();
if (pPrevNode_)
{
pPrevNode_->SetNextNode(pNextNode_); // SetNextNode同样添加了锁保护
}
if (pNextNode_)
{
pNextNode_->SetPrevNode(pPrevNode_); // SetPrevNode同样添加了锁保护
}
pPrevNode_ = 0;
pNextNode_ = 0;
UnLock();
}
这第二次尝试将我们对前向和后继结点的内部状态的直接修改改成了对其接口的调用,我们试图通过在其各种接口中加锁来达到多线程安全的目的。然而这却引入了新的问题,我们在一个被锁住的代码中进行了又调用了另外会使用锁的代码,这最可能引发的问题就是资源竞争,而在我们这次尝试中引如的问题的确就是资源竞争,导致死锁:
我们在不同线程中对结点1和结点2同时调用Break,当1申请到自身的锁之后,准备调用2的接口,此时2也申请到了自身的锁,准备调用1的接口。由于1已经占有了自身的锁,2也占有了自身的锁,那么1将会在调用2的接口的地方等待2的锁,而2将会在调用1的接口的地方等待1, 1和2的相互等待就形成了死锁。
第三次尝试:
void DoublyLinedNode::Break()
{
Lock();
if (pPrevNode_)
{
pPrevNode_-> Lock();
pPrevNode_->SetNextNode(pNextNode_);
pPrevNode_-> UnLock ();
}
if (pNextNode_)
{
pNextNode_-> Lock();
pNextNode_->SetPrevNode(pPrevNode_);
pNextNode_-> UnLock ();
}
pPrevNode_ = 0;
pNextNode_ = 0;
UnLock();
}
这次尝试显得比较愚蠢,将外部对象加锁的过程提到了自身Break当中效果和第二次尝试是一样的,没有得到任何的改善。
第四次尝试:
void DoublyLinedNode::Break()
{
SharedLock();
if (pPrevNode_)
{
pPrevNode_->SetNextNode(pNextNode_);
}
if (pNextNode_)
{
pNextNode_->SetPrevNode(pPrevNode_);
}
pPrevNode_ = 0;
pNextNode_ = 0;
SharedUnLock();
}
这次尝试取得了一定的成功,对于这些关系密切,存在相互调用的对象,我们使用了共享锁,它的确将我们的多线程访问冲突和死锁问题解决了,但是这个共享锁的实现难度是相当大的,你必须要保证可能产生相互调用的对象都要进行锁共享,那么你对于增加、修改、删除对象这些管理工作将会变得极度困难,稍有差池就会引发问题,而且别人在使用你的类的时候也同样需要处处小心,这不是我们所期望的。
以上我们进行了四次尝试将我们的双向链结点类设计成多线程安全,显然我们已经筋疲力尽,却未能达到满意的效果。
在这里我建议大家设计这种类的时候尽量设计成单线程模式,在框架设计中去考虑多线程问题,比如使用单线程访问对象,而模块间使用异步通信来进行交互等。
多线程编程的确非常困难,C++在这方面又表现得力不从心,我在这里引入这个问题旨在于告诫大家在对待多线程问题上一定要细心细心再细心。
【VS开发】C++线程安全的更多相关文章
- Linux下c开发 之 线程通信(转)
Linux下c开发 之 线程通信(转) 1.Linux“线程” 进程与线程之间是有区别的,不过Linux内核只提供了轻量进程的支持,未实现线程模型.Linux是一种“多进程单线程”的操作系统.Linu ...
- Android开发之线程池使用总结
线程池算是Android开发中非常常用的一个东西了,只要涉及到线程的地方,大多数情况下都会涉及到线程池.Android开发中线程池的使用和Java中线程池的使用基本一致.那么今天我想来总结一下Andr ...
- Linux下c开发 之 线程通信
Linux下c开发 之 线程通信 1.Linux“线程” 进程与线程之间是有区别的,不过Linux内核只提供了轻量进程的支持,未实现线程模型.Linux是一种“多进程单线程”的操作系统.Linux本身 ...
- Android开发之线程间通信
Android开发之线程间通信 当我们的软件启动的时候,计算机会分配进程给到我们运行的程序,在进程中包含多个线程用于提高软件运行速度. 在android网络请求中,我们知道在日常开发中不能在子线程中跟 ...
- C#开发的线程池和管理器 - 开源研究系列文章
上次编写了一个小软件,用于练手及自己的一个小工具集合.今天把其中的线程池和管理器的代码抽取出来,写成一个博文,让需要的朋友能够进行学习和应用. 这个线程管理器包括了3个类库和一个应用程序,见下图: 第 ...
- Android 开发笔记 “线程交互(Handler+Thread 和 AsyncTask)”
为什么需要线程 假设需要开发一个联网应用程序,需要从一个网址抓取网页内容,这里读取的网页地址是笔者在本地机器上自己建立的服务器地址.当然在读取网页内容的时候,可以使用HttpClient提供的API, ...
- iOS开发之线程间的MachPort通信与子线程中的Notification转发
如题,今天的博客我们就来记录一下iOS开发中使用MachPort来实现线程间的通信,然后使用该知识点来转发子线程中所发出的Notification.简单的说,MachPort的工作方式其实是将NSMa ...
- java游戏开发杂谈 - 线程
线程,让游戏拥有了动态变化的能力. java的图形界面,在启动的时候,就开始了一个线程. 这个线程负责处理:JFrame.JPanel等的绘制.事件处理. 它是由操作系统调用的,在程序启动时开启,程序 ...
- python开发_thread_线程_搜索本地文件
在之前的blog中,曾经写到过关于搜索本地文件的技术文章 如: java开发_快速搜索本地文件_小应用程序 python开发_搜索本地文件信息写入文件 下面说说python中关于线程来搜索本地文件 利 ...
- UE4-PS4开发渲染线程优化方法及记录
先说方法: Launch 到 PS4 Devkit上,在PS4上输入Stat unit 看瓶颈在哪里.我们发现Frame 和Draw数值几乎一样,其余两项相对较小,这表明瓶颈在渲染线程上. 关于渲染线 ...
随机推荐
- PyHook3----键盘鼠标操作
需要安装 pywin32 安装pyHook 下载网址:https://www.lfd.uci.edu/~gohlke/pythonlibs/#lxml 注意选择的pyHook版本一定要和python对 ...
- 【Winform-自定义控件】DataGridView 单元格合并和二维表头
DataGridView单元格合并和二维表头应用: //DataGridView绑定数据 DataTable dt = new DataTable(); dt.Columns.Add("); ...
- [Python之路] 元类(引申 单例模式)
一.类也是对象 当我们定义一个变量或者函数的时候,我们可以在globals()的返回值字典中找到响应的映射: def A(): print("This is function A" ...
- dataGridView添加ComboBox 每行绑定不同的集合,显示默认值
好了 多说无意,直接上代码,看不看的懂,就看大家的了,解决问题后,可以评论回复,可以一起商讨一些疑难杂症 List<ProtocolInfo> list = piDB.FindAll(). ...
- js拖拽文件夹上传
由于项目需要上传文件到服务器,于是便在文件上传的基础上增加了拖拽上传.拖拽上传当然属于文件上传的一部分,只不过在文件上传的基础上增加了拖拽的界面,主要在于前台的交互, 从拖拽的文件中获取文件列表然后调 ...
- ETL工具-KETTLE教程专栏1----术语和定义
1-资源库 资源库是用来保存转换任务的,用户通过图形界面创建的的转换任务可以保存在资源库中. 资源库可以使多用户共享转换任务,转换任务在资源库中是以文件夹形式分组管理的,用户可以自定义文 ...
- Selenium定位策略
1.通过XPath使用contains() 它将启动一个窗口,其中包含文本框开发中涉及的所有特定代码. 记下它的id属性. 通过XPath定位元素的语法 - 使用contains()可以写成: //& ...
- brute爆破
0X01明文传输的表单爆破用户名和密码 不存在任何加密 直接爆破即可 当不存在用户名时: 当存在用户名时,密码错误: 这里由于靶场关了 所以我们用dvwa演示 但是dvwa没有以上的差别 所以我们默认 ...
- mysql5.7以上基本配置
MySQL表名区分大小写设置 关闭MySQL服务 在服务运行目录找到my.ini或者my.cnf文件 find / -name my.cnf 打开文件,找到[mysqld]在下面增加一行 lower_ ...
- TensorFlow 学习(5)——进一步了解MNIST
接TensorFlow(3) 我们构建一个多层卷积网络,以提升MNIST的识别性能 权重初始化 为了创建这个模型,我们需要创建大量的权重和偏执项.这个模型中的权重在初始化是应该加入少量的噪声来打破对称 ...