Go死锁问题汇集

Mutex


这把全局锁把funcA从头锁到尾,funcA里面走一步某些case下会去调用funcB,funcB也需要抢到这个全局锁才能往下执行。这不就死锁了么

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package main

import (
"fmt"
"sync"
"time"
)

var mu sync.Mutex

func main() {

f1()
}

func f1() {

mu.Lock()

defer mu.Unlock()

fmt.Println("f1 抢到了锁")

f2()
time.Sleep(1e9)

}

func f2() {

mu.Lock()

defer mu.Unlock()

fmt.Println("f2 能抢到锁吗?")

}

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
f1 抢到了锁
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [sync.Mutex.Lock]:
sync.runtime_SemacquireMutex(0x14000076e68?, 0x80?, 0x1002614c0?)
/Users/fliter/fixgo/60000/go/src/runtime/sema.go:77 +0x28
sync.(*Mutex).lockSlow(0x10029bf60)
/Users/fliter/fixgo/60000/go/src/sync/mutex.go:171 +0x178
sync.(*Mutex).Lock(...)
/Users/fliter/fixgo/60000/go/src/sync/mutex.go:90
main.f2()
/Users/fliter/go/src/shuang/mutexx/deadlock.go:31 +0x90
main.f1()
/Users/fliter/go/src/shuang/mutexx/deadlock.go:24 +0x118
main.main()
/Users/fliter/go/src/shuang/mutexx/deadlock.go:13 +0x1c
exit status 2

这个一目了然,不难发现。但是当代码行数增加,调用关系复杂错节,任何不太容易出的问题都可能出现。

比如有20个func需要争抢一把全局锁(限制func之间的并行),但凡这些个func之间有一个存在层级关系(如第10个func有一步逻辑,成立时可能会调用第15个func),就妥妥死锁了。

我琢磨了一下,实现这样的一个检测工具并不困难,完全可以通过静态分析检测出来。…go vet按理该提供这样的一个分析器


Mutex with 某些方法


比如 os/exec包中的Wait()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Wait waits for the command to exit and waits for any copying to
// stdin or copying from stdout or stderr to complete.
//
// The command must have been started by Start.
//
// The returned error is nil if the command runs, has no problems
// copying stdin, stdout, and stderr, and exits with a zero exit
// status.
//
// If the command fails to run or doesn't complete successfully, the
// error is of type *ExitError. Other error types may be
// returned for I/O problems.
//
// If any of c.Stdin, c.Stdout or c.Stderr are not an *os.File, Wait also waits
// for the respective I/O loop copying to or from the process to complete.
//
// Wait releases any resources associated with the Cmd.
func (c *Cmd) Wait() error {
//...

}

Wait是等之前的命令执行结束(或出错),否则一直阻塞。。如果执行的这这个命令不退出,就是要一直运行,那就会一直阻塞在这里…..如果用mu.Lock和defer mu.Unlock锁了整个func,那因为Wait这里一直卡着,锁不会释放。。导致下一次想执行这个func时抢不到锁了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package main

import (
"fmt"
"os/exec"
"time"
)

func main() {

start := time.Now()
cmd := exec.Command("/bin/bash", "-c", "sleep 10")

stdout, err := cmd.StdoutPipe()
if err != nil {
fmt.Errorf("stdout error, %s", err.Error())
}
fmt.Println("stdout:", stdout)

cmd.Stderr = cmd.Stdout
if err := cmd.Start(); err != nil {
fmt.Errorf("cmd.Start error %+v \n", err)
}
pid := cmd.Process.Pid

fmt.Println("pid is:", pid)

cmd.Wait()

fmt.Println("10s后才会到这里..")
fmt.Println(time.Since(start))

}

输出:

1
2
3
4
stdout: &{0x14000066180}
pid is: 63111
10s后才会到这里..
10.026180375s