typevaluestruct{musync.Mutexvalueint}varwgsync.WaitGroupprintSum:=func(v1,v2*value){deferwg.Done()v1.mu.Lock()//在这里,我们尝试进入临界区来传入一个值。deferv1.mu.Unlock()//在这里,我们使用defer语句在printSum返回之前退出临界区。time.Sleep(2*time.Second)//在这里,我们休眠了一段时间来模拟一些工作(并触发死锁)v2.mu.Lock()deferv2.mu.Unlock()fmt.Printf("sum=%vn",v1.value+v2.value)}vara,bvaluewg.Add(2)goprintSum(&a,&b)goprintSum(&b,&a)wg.Wait()
如果尝试运行此代码,可能会看到:fatalerror:allgoroutinesareasleep-deadlock!
为什么呢?如果仔细观察,就可以在此代码中看到时机问题。以下是运行时的图形表示。这些框表示函数,水平线表示调用这些函数,竖线表示图形头部的函数生存时间,如下图所示。
图:一个因时间问题导致死锁的演示
本质上,我们创建了两个不能转动的齿轮:第一次调用printSum锁定a,然后试图锁定b,但在此期间,第二次调用printSum己锁定b并试图锁定a。这两个goroutine都无限地等待着。
以图形的方式展示为什么会出现死锁似乎很明确,但是更严格的定义会给我们带来更多的好处。事实证明,出现死锁有几个必要条件。1971年,EdgarCoffman在一篇论文中列举了这些条件。这些条件现在被称为Coffman条件,是帮助检测、防止和纠正死锁的技术依据。
Coffman条件如下:
相互排斥:井发进程同时拥有资源的独占权。
等待条件:并发进程必须同时拥有一个资源,并等待额外的资源。
没有抢占:并发进程拥有的资掘只能被该进程释放,即可满足这个条件。
循环等待:一个并发进程(P1)必须等待一系列其他井发进程(P2),这些并发进程同时也