多线程死锁及解决办法

死锁是由于不同线程按照不同顺序进行加锁而造成的。如:

线程Alock a加锁 => lock b加锁 => dosth => 释放lock b => 释放lock a

线程Block b加锁 => lock a加锁 => dosth => 释放lock a => 释放lock b

这样两条线程,就可能发生死锁问题。要避免发生死锁,应该使用同一个顺序进行加锁。

这点在对象单向调用的情况下是很容易达成的。对象单向调用的意思是如果对象a的函数调用了对象b的函数,则对象b中的函数不会去调用对象a的函数(注意:ab也可能同属于一个类)。

举个例子吧,假设聊天室(Room)对象room,聊天者(Chatter)对象chatter,假设ChatterRoom的定义如下:

class InnerChatter

{

public:

       void sendMsg(const string& msg)

       {

              boost::mutex::scoped_lock lock(mtx);

              socket->send(msg);

}

private:

       boost::mutex mtx;

       TcpSocket socket;

};

typedef boost::shared_ptr< InnerChatter> Chatter;

 

class InnerRoom

{

public:

       void sendMsg(const string& user, const string& msg)

       {

              boost::mutex::scoped_lock lock(mtx);

              if (chatters.find(user) != chatters.end())

              {

                     chatters[user]-> sendMsg(user);

              }

       }

private:

       boost::mutex mtx;

       map<string, Chatter> chatters;

};

 

目前代码中只存在Room调用Chatter的情况,不存在Chatter调用RoomRoom调用RoomChatter调用Chatter这三种情况。所以总是先获得room锁,再获得chatter锁,不会发生死锁。

如果为Chatter加上发送历史和以下这个方法之后呢?

vector<string> history;

void sendMsgToChatter(Chatter dst, const string& msg)

{

       boost::mutex::scoped_lock lock(mtx);   // 加锁当前对象

       history.push_back(msg);

       dsg>sendMsg(msg);      // 注意:次函数调用会加锁dst对象

}

乍看起来似乎没问题,但如果线程A执行chatterA.sendMsgToChatter(chatterB, “sth”)时,线程B正好执行chatterB.sendMsgToChatter(chatterA, “sth”),就会发生本文一开头举例的死锁问题。

如果在Chatter中加入函数:

void sendMsgToAll(Room room, const string& msg)

{

       boost::mutex::scoped_lock lock(mtx);

       history.push_back(msg);

       room->sendMsgToAll(msg);

}

Room中加入函数:

void sendMsgToAll(const string& msg)

{

       boost::mutex::scoped_lock lock(mtx);

       for (map<string, Chatter>::iterator it = chatters.begin(); it != chatters.end(); ++it)

       {

              it->second->sendMsg(msg);

       }

}

显然死锁问题更严重了,也更令人抓狂了。也许有人要问,为什么要这么做,不能就保持Room单向调用Chatter吗?大部分时候答案是肯定的,也建议大部分模块尤其是周边模块如基础设施模块使用明确清晰的单向调用关系,这样可以减少对死锁的忧虑,少白一些头发。

但有时候保证单向调用的代价太高:试想一下,如果被调用者b是一个容器类,调用者a定义了一些对元素的汇总操作如求和,为了避免回调(回调打破了单向调用约束),那就只有对b加锁,复制所有元素,解锁,遍历求和。复制所有元素比较耗计算资源,有可能成为性能瓶颈。

另外还有设计方面的考虑。还举RoomChatter的例子,如果避免Chatter调用RoomChatter,则Chatter很难实现啥高级功能,这样所有代码都将堆砌在RoomRoom将成为一个超级类,带来维护上的难度。此外还有设计上的不妥:因为几乎全部面向对象的设计模式都可以理解成某种方式的回调,禁止回调也就禁掉了设计模式,可能带来不便。

当对象间的相互调用无法避免时,如果只使用传统的mutex,保证相同顺序加锁需要十分小心,万一编程时失误,测试时又没发现(这是很可能的,死锁很不容易测试出来),如果条件允许还可以手忙脚乱地火线gdb,若无法调试定位,则服务器可能要成为重启帝了,对产品的形象十分有害。

我想出的解决方案是既然mutex要保证相同顺序加锁,就直接让mutex和一个优先级挂钩,使用线程专有存储(TSS)保存当前线程优先级最低的锁,当对新的mutex加锁时,如果mutex的优先级< 当前优先级(为什么=不可以,参考上文说的sendMsgToChatter函数),才允许加锁,否则记录当前函数栈信息,抛出异常(要仔细设计以免破坏内部数据结构)。代码如下:

 

boost::thread_specific_ptr<global::stack<int>> locks_hold_by_current_thread;

 

class xrecursive_mutex

{

public:           

       xrecursive_mutex(int pri_level_)

              : recursion_count(0)

              , pri_level(pri_level_){}

       ~xrecursive_mutex(){}        

 

       class scoped_lock

       {

       public:

              scoped_lock(xrecursive_mutex& mtx_)

                     : mtx(mtx_)

              {

                     mtx.lock();

              }

              ~scoped_lock()

              {

                     mtx.unlock();

              }

       private:          

              xrecursive_mutex& mtx;

       };

      

private:

       int recursion_count;

       int pri_level;

       boost::recursive_mutex mutex;

 

       int get_recursion_count()

       {                  

              return recursion_count;

       }

 

       void lock()

       {

              mutex.lock();

              ++ recursion_count;                   

              if (recursion_count == 1)

              {

                     if (locks_hold_by_current_thread.get() == NULL)

                     {

                            locks_hold_by_current_thread.reset(new std::stack<int>());                           

                     }

                     if (!locks_hold_by_current_thread->empty() &&

                            locks_hold_by_current_thread->top()>= pri_level)

                     {     //     wrong order, lock failed

                            -- recursion_count;

                            mutex.unlock();    

                            XASSERT(false);//记录栈信息,抛异常

                     }

                     locks_hold_by_current_thread->push(pri_level);

              }                  

       }

      

       void unlock()

       {

              bool bad_usage_flag = false;

              if (recursion_count == 1 &&locks_hold_by_current_thread.get() != NULL)

              {

                     if (!locks_hold_by_current_thread->empty()

                            && (locks_hold_by_current_thread->top() == pri_level))

                     {                         

                            locks_hold_by_current_thread->pop();

                     }    

                     else

                     {

                            bad_usage_flag = true;

                     }

              }

              -- recursion_count;

              mutex.unlock();    

              XASSERT(!bad_usage_flag);//      // 记录栈信息,抛异常

       }

 

};

 

使用:

xrecursive_mutex mtx1(1);

xrecursive_mutex mtx2(2);

xrecursive_mutex mtx3(3);

xrecursive_mutex mtx3_2(3);

{

       xrecursive_mutex::scoped_lock lock1(mtx1);      // pass, 当前线程锁优先级1

       xrecursive_mutex::scoped_lock lock2(mtx3);      // pass, 当前线程锁优先级3

       ASSERT_ANY_THROW(xrecursive_mutex::scoped_lock lock2_2(mtx3_2)); // 捕获异常,因为优先级3 <= 当前线程锁优先级

       xrecursive_mutex::scoped_lock lock3(mtx3);      // pass, 可重入锁

       xrecursive_mutex::scoped_lock lock4(mtx1);      // pass, 可重入锁

       ASSERT_ANY_THROW(xrecursive_mutex::scoped_lock lock5(mtx2)); // 捕获异常,因为优先级 2<= 当前线程锁优先级3

}


来源:http://blog.sina.com.cn/s/blog_48d4cf2d0100mx4n.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/464170.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

html:web前端开发规范

基本原则* 遵循内容&#xff08;HTML&#xff09;、显示&#xff08;CSS&#xff09;、行为&#xff08;JavaScript&#xff09;分离的代码组织模式 * 代码格式化&#xff0c;保持干净整洁 HTML部分2.1. 添加必须的utf-8的字符集&#xff0c;并且使用HTML5的简洁方式&#xff1…

离职就打低绩效,这样对吗?

应该不止听见一个人说过&#xff0c;担心离职遇到各种不爽的事情&#xff0c;比如卡你的离职时间&#xff0c;比如让你背很低的绩效&#xff0c;比如你今年的年终奖就没有了&#xff0c;再比如&#xff0c;你和原来玩得好的同事突然就没话说了。我记得很清楚的事情是&#xff0…

+ 网页制作效果常用代码

控制横向和纵向滚动条的显隐&#xff1f;<body style"overflow-y:hidden"> 去掉x轴<body style"overflow-x:hidden"> 去掉y轴<body scroll"no">不显表格变色<TD οnmοuseοver"this.style.backgroundColor#FFFFFF&qu…

设计一个串口服务器设备.《需求分析报告》,《项目开发,分布式多串口交换服务器的设计与实现...

摘要&#xff1a;随着信息技术和物联网技术的迅猛发展,TCP/IP网络应用呈现白热化趋势,各种以TCP/IP网络为主的通信设备已经成为主流,网络似乎无处不在.这种局面使得传统的以串行通信为主要通信方式的设备逐渐边缘化,然而要全部更换串行设备,成本开销太大,分布式多串口交换服务器…

设计模式的理解的总结

参考的博客&#xff1a;http://blog.csdn.net/wangeen/article/details/8272501 最近又把设计模式翻了一边&#xff0c;写个个人总结&#xff0c;这个总结比较简单&#xff0c;主要是一些理解&#xff0c;对于他们的实现和应用以后再总结 设计模式是编程抽象化的一个具体的应用…

PID算法原理介绍

先来彻底搞懂PID到底是啥&#xff1f;PID&#xff0c;就是“比例&#xff08;proportional&#xff09;、积分&#xff08;integral&#xff09;、微分&#xff08;differential&#xff09;”&#xff0c;是一种很常见的控制算法。在工程实际中&#xff0c;应用最为广泛的调节…

将python代码编译成.so文件

https://moonlet.gitbooks.io/cython-document-zh_cn/content/ch1-basic_tutorial.html add_num.pyx文件 def add_nums(ls):total 0.for l in ls:total lreturn totalset_up.py from distutils.core import setup from Cython.Build import cythonizesetup(ext_modules cyth…

将iphone中的照片同步到电脑

在windows中&#xff1a; 去我的电脑&#xff0c;点iphone的图标&#xff0c;打开直接就是照片的文件夹&#xff0c;可以进行各种操作&#xff1b; 在mac中&#xff1a; 使用iphoto软件&#xff0c;选择iphoto的菜单栏中“iphoto”->"performance"->"gene…

雅虎公司C#笔试题

雅虎公司C#笔试题&#xff0c;包括问答题和选择题两部分。试试看&#xff0c;你能回答出多少题&#xff0c;可以通过雅虎的考试吗&#xff1f;Question 1. (单选)在计算机网络中,表征数据传输可靠性的指标是1. 传输率2. 误码率3. 信息容量4. 频带利用率Question 2. (单选)以下关…

打败opencv ,哦,是快了3倍

大家好&#xff0c;本文转自我一个读者朋友Homio的文章&#xff0c;推荐给大家&#xff0c;希望对做这方便的同学有所帮助。程序员&#xff0c;哦&#xff01;不&#xff01;软件工程师们都对opencv很熟悉&#xff0c;它在工作学习研究中起到了不可或缺的作用。但是它臃肿的身躯…

Linux内核裁减

Linux内核裁减 &#xff08;1&#xff09;安装新内核: i)将新内核copy到/usr/src下, #tar xzvf linux-2.6.38.4.tar.gz -----解压缩. ii) 将名为linux的符号链接删掉,这是旧版本内核的符号链接. #ln -s linux-2.6.38.4 linux ------建立linux-2.6.38.4的符号链接linux. &#x…

python 将一个字符list的列表扁平化成了一个list

a[[app,ap,ade],[er],[bcc,brt]] 将这么一个list 转换成 a2[app,ap,ade,er,bcc,brt] 做法&#xff1a;a2[x for tup in a for x in tup] 转载于:https://www.cnblogs.com/cheng-cheng/p/10027446.html

数据库中使用自增量字段与Guid字段作主键的性能对比(补充篇)-----转

我在发表过“据库中使用自增量字段与Guid字段主键的性能对比”这篇文章后&#xff0c;得到博客园各园友的很多评价&#xff0c;大家对我的测试方法也提出一些改进的方法。让我吃惊的是一园友提出&#xff1a;把guid和id的测试顺序颠倒一下&#xff0c;看下结果。今天就再测试一…

JDBC操作数据库就这八步!

1.載入JDBC驅動程式 內含於JDK中&#xff0c;名稱為"Sun.jdbc.odbc.JdbcOdbcDriver"&#xff0c;可以使用"java.lang"套件下的 Class.forName()方法載入。 2.透過DriverManager類別建立Connection物件 於"java.sql"套件下可以運用到此JDBC API&a…

DEVC++出新版本了

昨天发了一篇文章C语言能判断一个变量是int还是float吗&#xff1f;然后有同学问我在Windows下是怎么写C代码的我是没有安装包的&#xff0c;只不过这个同学关系跟我不错&#xff0c;所以我就去找了下安装包&#xff0c;不找不知道&#xff0c;找了才发现&#xff0c;原来这个更…

iphone-common-codes-ccteam源代码 CCNSArray.h

//// CCNSArray.h// CCFC//// Created by xichen on 11-12-17.// Copyright 2011年 ccteam. All rights reserved.//#import <Foundation/Foundation.h>interface NSArray(cc)// 判断一个对象指针(不是对象的值)是否在数组中存在- (BOOL)isPointerExistsInArray:(id)…

TZOJ--1518: 星星点点 (二进制模拟)

1518: 星星点点 时间限制(普通/Java):1000MS/10000MS 内存限制:65536KByte 描述 输入一个由“*”和“.”组成的字符串&#xff0c;然后根据规则生成下一行字符串&#xff1a; 如果该行的第i和第i1个位置上的符号不同&#xff0c;则下一行的第i个位置上为“*”&#xff0c;…

关于反射的我的一些看法

关于反射的我的一些看法 最近因为项目的需要大量地使用了反射工厂,也看了大量的有关反射性能问题的讨论和各种测评.总的感觉是反射的性能太低,不适合在性能要求高的地方使用.但根据我多年的网站开发经验来看,一个WEB网站的性能关键一般只会是看数据表的设计是否合理,更进一步的…

EPOLL模型

最近在公司里面主要负责一些手机网络游戏的服务器端的编写。虽然接触时间不多&#xff0c;但是感触良多。特意将我此段时间的体会和大家分享。 以前公司的服务器都是使用HTTP连接&#xff0c;但是这样的话&#xff0c;在手机目前的网络情况下不但显得速度较慢&#xff0c;而且不…

小鹏汽车面试经验分享

大家周一好&#xff0c;这篇文章转自我的朋友李纳克斯&#xff0c;在做工作的同事&#xff0c;去面试也能增长自己的技术面和技术深度。推荐给大家&#xff0c;希望对大家有所帮助。某个下午&#xff0c;小鹏汽车的HR在招聘软件上撩我&#xff0c;于是我决定去聊聊看。接触下来…