【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数值几乎一样,其余两项相对较小,这表明瓶颈在渲染线程上. 关于渲染线 ...
随机推荐
- C/C++字符串转换函数;
字符串转数字: char 转 int 函数: atoi wchar 转 int 函数: _wtoi 数字转字符串: std::to_string(): std::to_wsting();
- 循环数组实现FIFO
涉及到数据通信的软件开发,不能回避的一点是,设计一个实用高效率的数据缓冲区,例如fifo.今天在做项目时候,需要缓存CAN总线上的数据,然后再需要的时候读这些数据.下边给出我自己设计的,采用循环数组实 ...
- Codeforces 1214 F G H 补题记录
翻开以前打的 #583,水平不够场上只过了五题.最近来补一下题,来记录我sb的调试过程. 估计我这个水平现场也过不了,因为前面的题已经zz调了好久-- F:就是给你环上一些点,两两配对求距离最小值. ...
- vue-cli 4.0.5 配置环境变量样例
在项目根目录下建 .env 文件,环境变量无论运行何种模式均可获取其设置值. vue 中主要有三种模式: development.test.production,在 package.json 中目前三 ...
- 26.Python三目运算符(三元运算符)用法详解
Python 可通过 if 语句来实现三目运算符的功能,因此可以近似地把这种 if 语句当成三目运算符.作为三目运算符的 if 语句的语法格式如下: True_statements if expres ...
- JavaWeb_(Struts2框架)使用Struts框架实现用户的登陆
JavaWeb_(Struts2框架)使用Servlet实现用户的登陆 传送门 JavaWeb_(Struts2框架)Servlet与Struts区别 传送门 MySQL数据库中存在Gary用户,密码 ...
- Linux sssd 进程 ldap 客户端配置
Linux sssd 进程 ldap 客户端配置 标签(空格分隔): ldap authconfig authconfig命令解析:authconfig 面对多计算机的身份管理以及账户信息同步, 其解 ...
- CSS效果篇--这里有你想要的CSS3漂亮的自定义Checkbox各种复选框
在原来有一篇文章写到了<CSS效果篇--纯CSS+HTML实现checkbox的思路与实例>.这篇文章主要写各种自定义的checkbox复选框,实现如图所示的复选框: 大致的html代码都 ...
- ZooKeeper分布式锁的实现原理
七张图彻底讲清楚ZooKeeper分布式锁的实现原理[石杉的架构笔记] 文章转载自:https://juejin.im/post/5c01532ef265da61362232ed#comment(写的 ...
- docker基础知识普及(一)
背景 这篇内容是之前给部门同事培训时写的文档,旨在传达一些docker相关概念,有个基本印象,当然,以下内容都来自网络,我只是个搬运工.具体操作在下篇文章中 一.什么是docker? 1. Docke ...