并行编程——锁

一. 死锁的成因

A deadlock scenario will always contain at least one deadlock cycle.

二.如何避免死锁

1.Global Locking Hierarchies

Locking hierarchies order the locks and prohibit acquiring locks out of order. In Figure 7.3, we might order the locks numerically, thus forbidding a thread from acquiring a given lock if it already holds a lock with the same or a higher number. Thread B has violated this hierarchy because it is attempting to acquire Lock 3 while holding Lock 4. This violation permitted the deadlock to occur. Again, to apply a locking hierarchy, order the locks and prohibit out-of-order lock acquisition. In large program, it is wise to use tools such as the Linux-kernel lockdep [Cor06a] to enforce your locking hierarchy.

2.Local Locking Hierarchies

由于全局层次锁无法应用于库函数。Local locking hierarchies应运而生。

(1)如果库函数不会调用Application的代码,那么代码执行流获取到library定义的锁后,就不会再去获取Application中定义的锁,这就不会引起程序库之间的死锁。

 (2)相反的如果库函数会调用Application的代码,那么代码执行流获取到library定义的锁C后,可能会再去获取Application中定义的锁B,这就会引起程序库之间的死锁。

 

这种类型的死锁该如何避免? 

The golden rule in this case is “Release all locks before invoking unknown code.”

if qsort() releases Lock C before invoking the comparison function, which is unknown code from qsort()’s perspective, then deadlock is avoided.

If each module(软件库) releases all locks before invoking unknown code, then deadlock is avoided if each module separately avoids deadlock. This rule therefore greatly simplifies deadlock analysis and greatly improves modularity.

 但是,往往在很多情况下,qsort()无法做到在调用cmp()之前释放掉所有的Library锁。这又该怎么办?

However, we can instead construct a layered locking hierarchy, as shown in Figure 7.7. here, the cmp() function uses a new Lock D that is acquired after all of Locks A, B, and C, avoiding deadlock. We therefore have three layers to the global deadlock hierarchy, the first containing Locks A and B, the second containing Lock C, and the third containing Lock D.

 针对Layered Locking Hierarchy举个例子:

 整个链表的工作机制如下:

找到链表结构struct locked_list,通过list_start(struct locked_list *lp)函数找到链表第一个节点的struct cds_list_head指针(struct cds_list_head可以假想成链表节点的挂钩)。然后通过struct cds_list_head这个挂钩以container_of的方式找到链表节点挂接的数据结构struct list_ints.

/* 其实实际上就是一个container_of */
#define cds_list_entry(ptr, type, member) \
	((type *) ((char *) (ptr) - (unsigned long) (&((type *) 0)->member)))

 链表示意图如下:

 只要用户代码在处理每个链表元素时,不要再去获取已经被其他调用list_start()或者list_next()的代码持有的锁——这会导致死锁。那么锁在用户代码里就可以继续保持隐藏。我们在给链表迭代器加锁时通过为锁的层次分级,就可以避免死锁的发生。

3.条件锁

(1)问题分析

假如某个场景设计不出合理的层次锁 —— 比如分层网络协议栈里,报文流是双向的,当报文从一个层传往另外一个层时,有可能需要在两层中同时获取锁(因为报文可以从协议栈上层往下层传,也可能相反,这简直时死锁的天然温床)。

这个例子中,当报文在协议栈中从上往下发送时,必须逆序获取下一层的锁。而报文在协议栈中从下往上发送时,是按顺序获取锁(Listing 7.3 line 4的获取锁操作将导致死锁——为什么?假设同时有个向上的报文正已经在layer 1处理完成,准备发往layer2。也就是说,此时spin_lock1已经被向上的报文处理线程获取,并且向上的报文处理线程正尝试获取spin_lock2;但是恰恰相反,向下的报文处理线程持有spin_lock2,正尝试获取spin_lock1,死锁就这样产生了)。

 

(2)解决方案

避免死锁的办法是先强加一套锁的层次,但是在必要时又可以又条件地乱序获取锁。该方案中使用了spin_trylock()有条件地获取锁。该原语在锁可用时立即获取锁,在锁不可用时不获取锁,返回0。

其实,核心思想就是 —— 通过spin_trylock()检查到死锁冲突,然后想法释放自己(下行线程)持有的锁,让上行线程可以正常执行,并释放自己(下行线程)想要持有地锁。随后自己(下行线程)再去获取需要的锁并正常向下执行。两个字总结就是 —— 让路

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

denglin12315

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值