死锁产生的条件有四个
1.互斥(mutual exclusion): 访问的资源必须是非共享的,A与B的访问要是互斥的,其他访问要等待
2.占有并等待(hold and wait):A占有一个资源并等待B占有的另一个资源
3.非抢占(no preemption):A占有的资源只有A自己去释放,不能被夺走
4.循环等待(circular wait):类似于哲学家就餐问题(A等待B占有的资源,B等待C占有的资源,C等待A占有的资源)- 必要条件(比如B占有的资源有多个实例,那么另一个需要相同的资源的就不会因为B占有了而出现死锁问题)
最终就是表现为资源的互相等待的循环
我们知道死锁产生了有一些方法去避免,下面这个问题就是通过加锁的顺序来避免死锁
指定加锁的顺序并不能真正的避免死锁因为调用的资源可能是通过外界传入进来的顺序,比如银行账户的每个账户都有一个锁,下面的方式通过锁的顺序是会出现死锁的
// 这些是伪代码
type Account struct {
sync.Mutex
nMoney uint64
nAccountID uint64
}
// one to one
func TranToHashCode(account Account) int {
return 0
}
// 取款
func WithDraw(account Account, nAmount uint64) {
}
// 存款
func Deposit(account Account, nAmount uint64) {
}
// 产生死锁的方式
// 如果有两个携程调用的话,并且调用顺序为
// Transaction(from, to,10)
// Transaction(to, from,10)
// 由于并发导致的交叉执行会产生死锁的可能
func Transaction(from Account, to Account, aMount uint64) {
from.Lock()
to.Lock()
WithDraw(from, aMount)
Deposit(to, aMount)
to.Unlock()
from.Unlock()
}
一种解决方案是认为的控制调用锁的顺序
// 解决死锁的方式
// 我们 通过给每个账号求一个hash值人为的改变顺序
func Transaction2(from Account, to Account, aMount uint64) {
var fromHashCode int = TranToHashCode(from)
var toHashCode int = TranToHashCode(to)
if fromHashCode < toHashCode {
from.Lock()
to.Lock()
WithDraw(from, aMount)
Deposit(to, aMount)
to.Unlock()
from.Unlock()
return
}
to.Lock()
from.Lock()
WithDraw(from, aMount)
Deposit(to, aMount)
from.Unlock()
to.Unlock()
}
上面这些都是伪代码,作为示例只是说明
举个mysql的例子
mysql也会有死锁的问题,当然原因肯定也是这四个条件满足才会出现,比如存储引擎innob批量更新数据库会有行锁,如果有两个更新任务里面有交叉的更新任务比如A[1,2,3,4] B[6,5,2,1]就有可能出现死锁问题 满足上面四个条件
第一、行锁 有相同的行会有互斥
第二、如果交叉执行A执行了1然后B去执行了6,5,2 紧接着又A开始执行就就先占有等待的问题
第三、因为A任务不会自己释放任务,就是等待执行完才会释放
第四、因为第二条的占有等待 A等待B B等待A 一直循环等待也就思索了
如果我们的update处理把更新序列排序也就是A[1,2,3,4] B[1,2,5,6]死锁就可以解决
因此预防可以从这四个必要条件去着手-----缺点是或导致性能问题