go语言中的协程(goroutine
)和通道(channel
)提供非常好的处理并发的方式,基于协程(goroutine
)和通道(channel
)可以将并发中的数据简单化。但用不好则会带来许多问题,死锁就是协程出现问题的一种表现形式。
那什么是死锁?按照我对go死锁的理解,就是在程序中的代码被阻塞
了,运行不下去,导致程序报错。
在go语言中,主要存在这几种死锁情况
package main
import "fmt"
func main() {
c := make(chan int)
c <- 1 // 我被阻塞了
fmt.Println("i am locked")
}
对于无缓冲通道c
,在主协程中将1
赋值给了他,但并没有在其他协程中被使用,导致程序被阻塞。
package main
import "fmt"
func main() {
c := make(chan int)
c <- 1 // 我在这里就死锁了
fmt.Println("i am locked")
go func() {
fmt.Println(<-c)
}()
}
虽然这里创建了一个匿名协程,使用并提取了无缓冲通道c
里面的值,但程序还是死锁了,代码始终停留在上面。
package main
import "fmt"
func main() {
c := make(chan int)
go func() {
fmt.Println(<-c)
}()
c <- 1
fmt.Println("i am free")
}
这里把创建子协程的时机提前了,在子协程中其实代码是被阻塞的,但这并不影响主协程的运行,当无缓冲通道c
被赋值,则会在子协程中打印协程c
携带的值。
只要主协程不被阻塞,程序就不会出现死锁,子协程被阻塞不会造成程序死锁
package main
import "fmt"
func main() {
c1 := make(chan int)
c2 := make(chan int)
go func() {
fmt.Println(<-c1)
fmt.Println(<-c2)
}()
c2 <- 2
c1 <- 1
fmt.Println("i am locked")
}
这里2被赋值给通道c2
,于是到子协程中寻找c2
,但在子协程中代码阻塞在c1
这,导致主协程在等待子协程执行通道c2
的操作,子协程在等待主协程给通道c1
赋值的操作,两个协程互相等待,最终整个程序被阻塞,报死锁。
package main
import "fmt"
func main() {
c1 := make(chan int)
c2 := make(chan int)
go func() {
fmt.Println(<-c1)
fmt.Println(<-c2)
}()
c1 <- 1
c2 <- 2
fmt.Println("i am free")
}
这里调换了c1
和c2
被赋值的顺序,程序能顺利运行。
package main
import "fmt"
func main() {
c1 := make(chan int, 2)
c1 <- 0
c1 <- 1
for ch := range c1 {
fmt.Println(ch)
}
fmt.Println("i am locked")
}
缓冲通道容量被遍历完的时候相当成了无缓冲通道,而在主协程这就造成了代码的阻塞,导致程序死锁,这和第一种是差不多的情况。