golang 死锁情况总结

1. 什么是死锁? 

死锁并不是锁的一种,而是一种错误使用锁导致的现象,死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。系统发生死锁现象不仅浪费大量的系统资源,甚至导致整个系统崩溃,带来灾难性后果。所以,对于死锁问题在理论上和技术上都必须予以高度重视。

2.死锁的机制

必须予以高度重视。在计算机组成原理里说过 死锁有三个必要条件他们分别是 循环等待、资源共享、非抢占式,在并发中出现通道死锁只有两种情况:

数据要发送,但是没有人接收

数据要接收,但是没有人发送

3.死锁情形

3.1.单协程自己死锁

fatal error: all goroutines are asleep - deadlock!

package main

import (
    "fmt"
)

func main() {
    // 没有人接收/后者接收者在程序后面,死锁
    // 这种方式可以演变成更多类型,比如读写锁相互阻塞,形成隐形死锁;管道读写时,相互要求对方先读/写等
    {
        ch := make(chan int)
        ch <- 10
        x := <- ch
        fmt.Println(x)
    }
}

 

跨主子多协程不死锁
 package main

import (
	"fmt"
	"time"
)

func main() {
	// 跨主协程/子协程通信不死锁
	{
		ch1 := make(chan string)
		go func() {
			ch1 <- "ch1 value"
		}()
		<- ch1
		fmt.Println("111222222")
	}

 

子协程不会死锁
 package main

import (
	"fmt"
)

func main() {
	{
		go func(){
			ch := make(chan int)
			ch <- 10
			x := <- ch
			fmt.Println(x)
		}()

		time.Sleep(3 * time.Second)
		fmt.Println("finished")
	}

 

3.2.发送个数超过缓冲区个数死锁

fatal error: all goroutines are asleep - deadlock!

package main

import (
)

func main() {
    ch := make(chan int, 1)
    ch <- 10
    ch <- 11
}

3.3.select没有任何处理,死锁

fatal error: all goroutines are asleep - deadlock!

package main

import (
)

func main() {
    select{
    }
}

3.4.多channel相互交叉依赖,死锁

fatal error: all goroutines are asleep - deadlock!此时主协程和子协程,互相掌握着各自的条件,但是都无法释放,造成死锁

package main

import (
	"fmt"
)

func main() {
    ch1 := make(chan int)
    ch2 := make(chan int)
    go func() {
        for {
            select {
                case num:=<-ch1:
                    ch2 <- num
            }
        }
    }()
    for {
        select {
            case num:=<-ch2:
                ch1 <- num
        }
    }
}

3.5.将 互斥锁、读写锁 与 channel 混用。 —— 隐性死锁

下面代码造成隐形死锁的原因是,进行readGo函数时,以读的模式加锁,同时 channel 等待数据写入,以便于数据的读出,此时channel等到数据写入造成堵塞,下面代码无法执行,造成以读模式的锁无法解锁。但进入到writeGo函数中时,向下执行时,遇到以写模式加锁时会造成堵塞,因为上方以读的模式的锁并没有解锁,造成以写模式加锁下方的代码无法执行,无法向channel中写入数据,使其互相堵塞,但是控制台并不报错,该死锁称为隐性死锁。

 

简单地说,读写锁就是一种能保证:

  1. 并发读操作之间不互斥;
  2. 并发写操作之间互斥;
  3. 并发读操作和写操作互斥;

 

package main

import (
	"fmt"
	"math/rand"
	"sync"
	"time"
)
var rwMutex sync.RWMutex
//读时共享,写时独占
func readGo(in <- chan int,idx int)  {
	for {
		rwMutex.RLock()		//以读模式加锁
		num:=<-in
		fmt.Println("----%dth 读go程,读出:%d\n",idx,num)
		rwMutex.RUnlock()	//以读模式解锁
	}
}
func writeGo(out chan <- int,idx int)  {
	for {
		num := rand.Intn(1000)
		rwMutex.Lock()	//以写模式加锁
		out <- num
		fmt.Println("%dth 写Go程,写入:%d\n",idx,num)
		time.Sleep(time.Millisecond * 300)
		rwMutex.Unlock()	//以写模式解锁
	}
}
func main()  {
	ch:=make(chan int)
	for i:=0;i<5;i++ {
		go readGo(ch,i+1)
	}
	for i:=0;i<5;i++ {
		go writeGo(ch,i+1)
	}

	for{
		time.Sleep(1 * time.Second)
	}
}

 

posted @ 2023-02-07 15:33  若-飞  阅读(205)  评论(0编辑  收藏  举报