在学习go语言过程中,有一个比较难以理解的知识点就是并发(go routine)以及通信通道(channel)。channel是为了使go语言更高效地并发,避免容易出错,避免负责的共享内存操作,而设计出来的。但是我们在编写代码过程中,经常出现死锁。那么,有没有一种比较容易理解的方式来理解go routine, chan之间的关系,从而避免死锁呢?有的,可以以一种简单的思路理解死锁是如何产生的。
简单地说,就是go routine必须在main函数使用chan之前。
先看下面4个例子:
例子1
package main
import (
“fmt”
)
func f1(in chan int) {
in <- 2
}
func main() {
out := make(chan int)
go f1(out)
fmt.Println(<-out)
}
运行结果:2。正常运行,go routine在main使用chan之前调用。
例子2
package main
import (
“fmt”
)
func f1(in chan int) {
in <- 2
}
func main() {
out := make(chan int)
fmt.Println(<-out)
go f1(out)
}
运行结果:fatal error: all goroutines are asleep - deadlock!
main函数在go routine之前使用chan
例子3
package main
import (
“fmt”
)
func f1(in chan int) {
fmt.Println(<-in)
}
func main() {
out := make(chan int)
out <- 2
go f1(out)
}
运行结果:fatal error: all goroutines are asleep - deadlock!
例子4
package main
import (
“fmt”
)
func f1(in chan int) {
fmt.Println(<-in)
}
func main() {
out := make(chan int)
go f1(out)
out <- 2
}
运行结果:2
进一步思考,如果使用chan的代码都是放在go routine,那会怎样呢?答案是:正常。
例子5
package main
import (
“fmt”
“time”
)
func f1(in chan int) {
in <- 2
}
func f2(ch chan int) {
fmt.Println(<-ch)
}
func main() {
out := make(chan int)
go f2(out)
go f1(out)
time.Sleep(1 * time.Second)
}
运行结果:2
例子6
package main
import (
“fmt”
“time”
)
func f1(in chan int) {
in <- 2
}
func f2(ch chan int) {
fmt.Println(<-ch)
}
func main() {
out := make(chan int)
go f1(out)
go f2(out)
time.Sleep(1 * time.Second)
}
运行结果:2
其中例子5和6中的main函数都在最后添加睡眠等待函数,睡眠1秒,这个是等待go routine完成,避免main函数直接退出。
总之一句:go routine必须在main函数使用chan之前。这种简易的理解方式可以扩展一下,那就是main函数在使用chan时(其实就是堵塞通信),应该要有go routine活着。
请看例子7
package main
import (
“fmt”
)
func main() {
forever := make(chan int)
go func() {
fmt.Println(“In the go routine.”)
}()
fmt.Println(“In the main, waiting forever.”)
<-forever
}
在这个例子中,只有main函数使用了forever这个chan。go routine打印一行语句就直接退出,相当于没有。
运行结果:
In the main, waiting forever.
In the go routine.
fatal error: all goroutines are asleep - deadlock!
同理,例子8
package main
import (
“fmt”
“time”
)
func main() {
forever := make(chan int)
go func() {
for true {
fmt.Println(“In the go routine.”)
time.Sleep(3 * time.Second)
}
}()
fmt.Println(“In the main, waiting forever.”)
<-forever
}
由于main函数等待forever这个永远没有输入的通信输出,永远等不到,但是go routine一直无限循环,所以一直运行下去。这个是很多服务器的写法。
运行结果:
In the main, waiting forever.
In the go routine.
In the go routine.
。。。。。
你看go语音的go routine,channel,和死锁之间的关系就是这么简单!