【Go】go中的死锁

go语言中的协程(goroutine)和通道(channel)提供非常好的处理并发的方式,基于协程(goroutine)和通道(channel)可以将并发中的数据简单化。但用不好则会带来许多问题,死锁就是协程出现问题的一种表现形式。
那什么是死锁?按照我对go死锁的理解,就是在程序中的代码被阻塞了,运行不下去,导致程序报错。

在go语言中,主要存在这几种死锁情况

1.无缓冲通道的死锁

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携带的值。
只要主协程不被阻塞,程序就不会出现死锁,子协程被阻塞不会造成程序死锁

2.两个通道中的死锁

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")
}

这里调换了c1c2被赋值的顺序,程序能顺利运行。

3.缓冲通道的死锁

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")
}

缓冲通道容量被遍历完的时候相当成了无缓冲通道,而在主协程这就造成了代码的阻塞,导致程序死锁,这和第一种是差不多的情况。

你可能感兴趣的:(go)