我们是多么渴望各种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++线程安全的更多相关文章

  1. Linux下c开发 之 线程通信(转)

    Linux下c开发 之 线程通信(转) 1.Linux“线程” 进程与线程之间是有区别的,不过Linux内核只提供了轻量进程的支持,未实现线程模型.Linux是一种“多进程单线程”的操作系统.Linu ...

  2. Android开发之线程池使用总结

    线程池算是Android开发中非常常用的一个东西了,只要涉及到线程的地方,大多数情况下都会涉及到线程池.Android开发中线程池的使用和Java中线程池的使用基本一致.那么今天我想来总结一下Andr ...

  3. Linux下c开发 之 线程通信

    Linux下c开发 之 线程通信 1.Linux“线程” 进程与线程之间是有区别的,不过Linux内核只提供了轻量进程的支持,未实现线程模型.Linux是一种“多进程单线程”的操作系统.Linux本身 ...

  4. Android开发之线程间通信

    Android开发之线程间通信 当我们的软件启动的时候,计算机会分配进程给到我们运行的程序,在进程中包含多个线程用于提高软件运行速度. 在android网络请求中,我们知道在日常开发中不能在子线程中跟 ...

  5. C#开发的线程池和管理器 - 开源研究系列文章

    上次编写了一个小软件,用于练手及自己的一个小工具集合.今天把其中的线程池和管理器的代码抽取出来,写成一个博文,让需要的朋友能够进行学习和应用. 这个线程管理器包括了3个类库和一个应用程序,见下图: 第 ...

  6. Android 开发笔记 “线程交互(Handler+Thread 和 AsyncTask)”

    为什么需要线程 假设需要开发一个联网应用程序,需要从一个网址抓取网页内容,这里读取的网页地址是笔者在本地机器上自己建立的服务器地址.当然在读取网页内容的时候,可以使用HttpClient提供的API, ...

  7. iOS开发之线程间的MachPort通信与子线程中的Notification转发

    如题,今天的博客我们就来记录一下iOS开发中使用MachPort来实现线程间的通信,然后使用该知识点来转发子线程中所发出的Notification.简单的说,MachPort的工作方式其实是将NSMa ...

  8. java游戏开发杂谈 - 线程

    线程,让游戏拥有了动态变化的能力. java的图形界面,在启动的时候,就开始了一个线程. 这个线程负责处理:JFrame.JPanel等的绘制.事件处理. 它是由操作系统调用的,在程序启动时开启,程序 ...

  9. python开发_thread_线程_搜索本地文件

    在之前的blog中,曾经写到过关于搜索本地文件的技术文章 如: java开发_快速搜索本地文件_小应用程序 python开发_搜索本地文件信息写入文件 下面说说python中关于线程来搜索本地文件 利 ...

  10. UE4-PS4开发渲染线程优化方法及记录

    先说方法: Launch 到 PS4 Devkit上,在PS4上输入Stat unit 看瓶颈在哪里.我们发现Frame 和Draw数值几乎一样,其余两项相对较小,这表明瓶颈在渲染线程上. 关于渲染线 ...

随机推荐

  1. Python之exec()/compile()方法使用

    # Python内置函数exec()可以用来执行Python代码 # 或内置函数compile()编译的代码对象 # exec程序中可写入python语法格式的代码,并直接输出. exec('prin ...

  2. 3 触发器报警-->远程执行命令

    0.需求 上节课我们讲了,触发器报警,发送邮件,这节课主要讲下远程执行命令 流程图如下 item--> triggers-->action--->Email     |——>远 ...

  3. Linux 目录共享

    ## 安装 nfs 和 rpc yum install -y nfs-utils rpcbind ## ubuntu 安装 nfs 和 rpc ## apt-get install nfs-kerne ...

  4. python 通过序列索引迭代

    另外一种执行循环的遍历方式是通过索引,如下实例: #!/usr/bin/python # -*- coding: UTF-8 -*- fruits = ['banana', 'apple', 'man ...

  5. 医院设置x

    医院设置x   题目描述 Description 设有一棵二叉树,如下图 其中,圈中数字表示结点居民的人口.圈边上数字表示结点编号,.现在要求在某个结点上建立一个医院,使所有居民所走的路程之和为最小, ...

  6. [JOI2012春季合宿]Rotate (链表)

    题意 题解 又是一道神仙题-- 显然的做法是大力splay,时间复杂度\(O((N+Q)N\log N)\), 可以卡掉. 正解: 使用十字链表维护矩阵,在周围增加第\(0\)行/列和第\((n+1) ...

  7. jmeter下载安装

    jmeter运行依靠java环境 一.根据jmeter版本不同要求java环境则不同 jmeter官网下载地址:http://jmeter.apache.org/download_jmeter.cgi ...

  8. $\LaTeX$数学公式大全10

    $10\ Array\ environment,examples$ $\left( \begin{array}{cc} 2\tau & 7\phi-frac5{12} \\ 3\psi &am ...

  9. ARTS打卡计划第五周

    Algorithms: https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/ 采用了map的 ...

  10. elasticsearch 单实例安装启动

    elasticsearch 初次启动 下载 elasticsearch-6.3.2.tar.gz 创建目录 /usr/local/elasticsearch/ 解压 tar -zxf elastics ...