前言

最近写着写着遇到些稀奇古怪的问题,想着与defer、GOTO以及锁有关,于是整理了一下。

切入正题

首先来看看本文的主角们

十分好用的defer

func main() {
    doSomething()
    defer finishDoSomething()   
    println("Something has been done")
}

func doSomething() {
    println("Do something")
}

func finishDoSomething() {
    println("Finish do something")
}

这是一个defer的用例,很简单,就是在doSomething结束之后,再执行finishDOsomethingprintln输出到stderr,fmt.Println输出到stdout,我绝对不是为了偷懒少打点才用的println),以上代码的输出如下:

Do something
Something has been done
Finish do somethin

没有任何问题,十分正常且符合预期的输出。

掺和一下goto,顺便加了点料

现在笔者想在doSomething结束后,从头来过,再来一遍,为了便于区分,顺便加了点其他料:

func main() {
    restart := true
    runningTimes := 1
INIT:
    doSomething(runningTimes)
    defer finishDoSomething(runningTimes)
    println("Something has been done ", runningTimes, " time")
    if restart {
        println("Star Over")
        restart = false
        runningTimes++
        goto INIT
    }
}

func doSomething(time int) {
    println("Do something ", time, " time")
}

func finishDoSomething(time int) {
    println("Finish do something ", time, " time")
}

此时程序的输出为

Do something  1  time
Something has been done  1  time
Star Over
Do something  2  time
Something has been done  2  time
Finish do something  2  time
Finish do something  1  time

前五行看下来似乎都没什么问题,着重看最后两行,为什么runningTimes变成2之后仍旧打出了
Finish do something 1 time呢?
这里其实与goto没有任何关系,是defer的特性,当程序经过defer时,会进行参数预计算,也就是说,当runTimes是1时,加载到defer finishDoSomething(runningTimes)的时候,就会把finishDoSomething(1)丢到栈里,所以最后可以打印出来Finish do something 1 time
然后是打印次序及顺序的问题,把刚刚的执行过程用代码重新梳理一下可能就会好理解地多:

doSomething(1)
defer finishDoSomething(1)
println("Something has been done ", 1, " time")
println("Star Over")
doSomething(2)
defer finishDoSomething(2)
println("Something has been done ", 2, " time")

所以,goto 之前有defer的照样会执行,很显然,在这个例子里执行了两次defer(当然预计算的值不一样)。

此时一个人畜无害的Mutex混入其中

此时,出于某种原因,在doSomething的时候,必须对locker进行锁定,好让其他人不能同时一起doSomething,稍加修改:

func main() {
	// 不想多写一个goroutine绝对不是我懒
    restart := true
    runningTimes := 1
    locker := sync.Mutex{}
INIT:
    locker.Lock()
    doSomething(runningTimes)
    defer func() {
        finishDoSomething(runningTimes)
        defer locker.Unlock()
    }()
    println("Something has been done ", runningTimes, " time")
    if restart {
        println("Star Over")
        restart = false
        runningTimes++
        goto INIT
    }
}

func doSomething(time int) {
    println("Do something ", time, " time")
}

func finishDoSomething(time int) {
    println("Finish do something ", time, " time")
}

此时执行此程序,似乎出现了不得了的问题(经典故弄玄虚环节)

Do something  1  time
Something has been done  1  time
Star Over
fatal error: all goroutines are asleep - deadlock!

竟然出现了死锁!(似乎也不必这么惊讶)
这里面的死锁其实是由于锁重入导致的,Golang是不支持重入锁的,这就会导致死锁。
已知了原因似乎也很好解决了,大不了goto前解锁呗:

func main() {
	// 不想多写一个goroutine绝对不是我懒
    restart := true
    runningTimes := 1
    locker := sync.Mutex{}
INIT:
    locker.Lock()
    doSomething(runningTimes)
    defer func() {
        finishDoSomething(runningTimes)
        defer locker.Unlock()
    }()
    println("Something has been done ", runningTimes, " time")
    if restart {
        println("Star Over")
        restart = false
        runningTimes++
        locker.Unlock()
        goto INIT
    }
}

func doSomething(time int) {
    println("Do something ", time, " time")
}

func finishDoSomething(time int) {
    println("Finish do something ", time, " time")
}

于是梅开二度:

Do something  1  time
Something has been done  1  time
Star Over
Do something  2  time
Something has been done  2  time
Finish do something  2  time
Finish do something  2  time
fatal error: sync: unlock of unlocked mutex

确实,解决了重入问题,但是没有解决Unlock两次的问题。
从程序跑通的角度来看,利用defer的特性再加一个defer locker.Lock()便可解君愁了:

func main() {
	// 不想多写一个goroutine绝对不是我懒
    restart := true
    runningTimes := 1
    locker := sync.Mutex{}
INIT:
    locker.Lock()
    doSomething(runningTimes)
    defer func() {
        finishDoSomething(runningTimes)
        defer locker.Unlock()
    }()
    println("Something has been done ", runningTimes, " time")
    if restart {
        println("Star Over")
        restart = false
        runningTimes++
        locker.Unlock()
        defer locker.Lock()
        goto INIT
    }
}

func doSomething(time int) {
    println("Do something ", time, " time")
}

func finishDoSomething(time int) {
    println("Finish do something ", time, " time")
}

成功了!它跑起来了!开心!(棒读)
原本加locker的目的在于执行本段代码的时候,有且只有一个goroutine在执行,但是在上述的修改中,更加积重难返,为了Unlock特意Lock这种操作其实大可不必,到这种程度了,修改原来代码的逻辑其实是更好的选择,比如:

func main(){
    restart := true
    runningTimes := 1
    locker := sync.Mutex{}
    locker.Lock()
    defer locker.Unlock()
INIT:
    doSomething(runningTimes)
    defer func() {
        finishDoSomething(runningTimes)
    }()
    println("Something has been done ", runningTimes, " time")
    if restart {
        println("Star Over")
        restart = false
        runningTimes++
        goto INIT
    }    
}

又或者您坚持需要一个可以重入的锁建议自己写一个,可以参考一下以下解决方案:
Go语言如何实现可重入锁? - SegmentFault 思否