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

线程A:对lock a加锁 => 对lock b加锁 => dosth => 释放lock b => 释放lock a

线程B:对lock b加锁 => 对lock a加锁 => dosth => 释放lock a => 释放lock b

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

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

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

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调用Room,Room调用Room,Chatter调用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加锁,复制所有元素,解锁,遍历求和。复制所有元素比较耗计算资源,有可能成为性能瓶颈。

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

当对象间的相互调用无法避免时,如果只使用传统的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

多线程死锁及解决办法相关推荐

  1. HBase 高性能获取数据(多线程批量式解决办法) + MySQL和HBase性能测试比较

    转载于:http://www.cnblogs.com/wgp13x/p/4245182.html 摘要:   在前篇博客里已经讲述了通过一个自定义 HBase Filter来获取数据的办法,在末尾指出 ...

  2. 数据库死锁及解决办法

    文章目录 1. 事务之间对资源访问顺序的交替 2. 并发修改同一记录 3. 索引不当导致的死锁 目前,我们已经探讨了许多关于数据库锁的问题,锁能够有效地解决并发的问题,但这也带来了一个严重的缺点,那就 ...

  3. 数据库死锁的解决办法

    一.破坏死锁的四个必要条件 二.如果发生死锁,通过SQL语句关闭当前发生死锁的数据库进程 USE master --不能用 KILL 来取消您自己的进程. GO /****** Object:  St ...

  4. 如何查询oracle死锁,Oracle死锁查看和解决办法汇总

    由于生产的tomcat 经常有假死问题,困扰很久,最后发现有死锁,解决办法分享 1.查看死锁 1)用dba用户执行以下语句select username,lockwait,status,machine ...

  5. 面试问题之操作系统:死锁的四个必要条件和解决办法

    面试问题之操作系统:死锁的四个必要条件和解决办法 参考文章: (1)面试问题之操作系统:死锁的四个必要条件和解决办法 (2)https://www.cnblogs.com/yichengming/p/ ...

  6. linux db2表死锁,记录一次问题解决:DB2死锁解决办法(SQLCODE=-911, SQLSTATE=40001)

    (DB2的数据库)在做update更新的时候,发生了死锁.后台报的错误为:SQLCODE=-911, SQLSTATE=40001 ---------------------------------- ...

  7. 一个多线程死锁案例,如何避免及解决死锁问题?

    多线程死锁在java程序员笔试的时候时有遇见,死锁概念在之前的文章有介绍,大家应该也都明白它的概念,不清楚的去翻看历史文章吧. 下面是一个多线程死锁的例子 public class lock{priv ...

  8. mysql死锁解决办法

    现象:使用alter修改表结构卡住不动,使用drop删除表卡住不动,使用drop删除数据库卡住不动 原因:由于不同的访问连接,争取同一资源,导致进程死锁 解决办法:杀掉正在连接此数据库的进程 具体操作 ...

  9. 多线程内存泄漏_内存泄漏的场景和解决办法

    1.非静态内部类会持有外部类的引用,如果非静态内部类的实例是静态的,就会长期的维持着外部类的引用,组织被系统回收,解决办法是使用静态内部类 2.多线程相关的匿名内部类和非静态内部类 匿名内部类同样会持 ...

最新文章

  1. log4j:WARN Error initializing output writer. log4j:WARN Unsupported encoding?
  2. linq to sql简单使用
  3. python MySQL 插入Elasticsearch
  4. 0网卡开启_中标麒麟Linux v7系统下设置双网卡bond或team绑定详细过程
  5. 图片测量尺寸软件_3D扫描之工件测量检测
  6. C++中内存泄漏的检测方法介绍
  7. 计算机学院实验室安全管理办法,江苏大学计算机学院实验室安全管理制度
  8. gateway-统一权限-认证
  9. 电信sdn虚拟服务器,数据中心SDN网络的构建及通信业务与光纤引入
  10. john破解kali密码
  11. 用python编写程序实现分段函数的计算_编写程序,实现分段函数计算,如下表所示。 x y x0 0 0=x5 x 5=x10 3x-5 10=x20 0.5x-2 20=x 0_学小...
  12. 云计算实训报告总结_实训报告心得体会(通用5篇)
  13. Elasticsearch:使用向量搜索来查询及比较文字 - NLP text embedding
  14. PHP程序员开发win32应用程序之梦
  15. RIGHT-BICEP单元测试——“二柱子四则运算升级版”
  16. 《InnoDB存储引擎》第五章——索引与算法
  17. 转:latex 表格紧跟指定的文字后面
  18. css textarea行数_超级简单:在一个TextArea中如何限制行数和字符数
  19. 科学动画制作工具——Blender
  20. 博图 路径字符串不正确_博图练习

热门文章

  1. Lombok 使用小结
  2. 启动页面和各设备的宽高比及像素
  3. BSD配置SSH服务
  4. 〖Linux〗穿越城墙之后,直接连接国内网站的路由配置
  5. Web安全渗透测试之信息搜集篇(上)
  6. 如何选择合适的Web安全网关?
  7. java中 flush()方法
  8. Diango博客--7.自动生成文章摘要
  9. 飞控计算机的作用,用于波音777飞机的主要飞控计算机
  10. 深度学习之 BP 算法