首页 > 其他分享 >golang中关于死锁的思考与学习

golang中关于死锁的思考与学习

时间:2024-09-09 13:46:36浏览次数:9  
标签:ch 思考 阻塞 golang 死锁 func main channel

1、Golang中死锁的触发条件

1.1 书上关于死锁的四个必要条件的讲解

发生死锁时,线程永远不能完成,系统资源被阻碍使用,以致于阻止了其他作业开始执行。在讨论处理死锁问题的各种方法之前,我们首先深入讨论一下死锁特点。

必要条件:

如果在一个系统中以下四个条件同时成立,那么就能引起死锁:

  1. 互斥:至少有一个资源必须处于非共享模式,即一次只有一个线程可使用。如果另一线程申请该资源,那么申请线程应等到该资源释放为止。
  2. 占有并等待:—个线程应占有至少一个资源,并等待另一个资源,而该资源为其他线程所占有。
  3. 非抢占:资源不能被抢占,即资源只能被线程在完成任务后自愿释放。
  4. 循环等待:有一组等待线程 {P0,P1,…,Pn},P0 等待的资源为 P1 占有,P1 等待的资源为 P2 占有,……,Pn-1 等待的资源为 Pn 占有,Pn 等待的资源为 P0 占有。

我们强调所有四个条件必须同时成立才会出现死锁。循环等待条件意味着占有并等待条件,这样四个条件并不完全独立。

图示例:

 

 

线程1、线程2都尝试获取对方未释放的资源,从而会一直阻塞,导致死锁发生。

1.2 Golang 死锁的触发条件

看完了书上关于死锁的介绍,感觉挺清晰的,但是实际上到了使用或者看代码时,自己去判断是否会发生死锁却是模模糊糊的,难以准确判断出来。所以特意去网上找了些资料学习,特此记录。

golang中死锁的触发条件:

死锁是当 Goroutine 被阻塞而无法解除阻塞时产生的一种状态。注意:for 死循环不能算在这里,虽然空for循环是实现了阻塞的效果,但是实际上goroutine是处于运行状态的。

1.3 golang 中阻塞的场景

1.3.1 sync.Mutex、sync.RWMutex

golang中的锁是不可重入锁,对已经上了锁的写锁,再次申请锁是会报死锁。上了读锁的锁,再次申请写锁会报死锁,而申请读锁不会报错。

写写冲突,读写冲突,读读不冲突。
func main() {
	var lock sync.Mutex
	lock.Lock()
	lock.Lock()
}   
//报死锁错误
func main() {
	var lock sync.RWMutex
	lock.RLock()
	lock.Lock()
}
//报死锁错误
func main() {
	var lock sync.RWMutex
	lock.RLock()
	lock.RLock()
}
//正常执行

1.3.2 sync.WaitGroup

一个不会减少的 WaitGroup 会永久阻塞。

func main() {
	var wg sync.WaitGroup
	wg.Add(1)
	wg.Wait()
  //报死锁错误
}

1.3.3 空 select

空 select 会一直阻塞。

package main

func main() {
	select {
	
	}
}
//报死锁错误

1.3.4 channel

为 nil 的channel 发送、接受数据都会阻塞。

func main() {
	var ch chan struct{}
	ch <- struct{}{}
}
//报死锁错误

无缓冲的channel 发送、接受数据都会阻塞。

func main() {
	ch := make(chan struct{})
	<- ch
}
//报死锁错误

channel 缓冲区满了的,继续发送数据会阻塞。

2、死锁案例讲解

2.1 案例一:空 select{}

package main

func main() {
	select {
	
	}
}

以上面为例子,select 语句会 造成 当前 goroutine 阻塞,但是却无法解除阻塞,所以会导致死锁。

2.2 案例二:从无缓冲的channel接受、发送数据

func main() {
	ch := make(chan struct{})
	//ch <- struct{}{} //发送
	<- ch //接受
	fmt.Println("main over!")
}

发生原因:

上面创建了一个 名为:ch 的channel,没有缓冲空间。当向无缓存空间的channel 发送或者接受数据时,都会阻塞,但是却无法解除阻塞,所以会导致死锁。

解决方案:边接受边读取

package main
 
// 方式1
func recv(c chan int) {
	ret := <-c
	fmt.Println("接收成功", ret)
}
func main() {
	ch := make(chan int)
	go recv(ch) // 启用goroutine从通道接收值
	ch <- 10
	fmt.Println("发送成功")
}
 
// 方式2
func main() {
   ch := make(chan int,1)
   ch<-1
   println(<-ch)
}

2.3 案例三:从空的channel中读取数据

package main

import (
	"fmt"
	"time"
)

func request(index int,ch chan<- string)  {
	time.Sleep(time.Duration(index)*time.Second)
	s := fmt.Sprintf("编号%d完成",index)
	ch <- s
}

func main() {
	ch := make(chan string, 10)
	fmt.Println(ch,len(ch))

	for i := 0; i < 4; i++ {
		go request(i, ch)
	}

	for ret := range ch{ //当 ch 中没有数据的时候,for range ch 会发生阻塞,但是无法解除阻塞,发生死锁
		fmt.Println(len(ch))
		fmt.Println(ret)
	}
}

发生原因:

当 ch 中没有数据的时候,就是从空的channel中接受数据,for range ch 会发生阻塞,但是无法解除阻塞,发生死锁。

解决办法:当数据发送完了过后,close channel

package main

import (
	"fmt"
	"sync"
	"time"
)

var wg sync.WaitGroup

func request(index int,ch chan<- string)  {
	time.Sleep(time.Duration(index)*time.Second)
	s := fmt.Sprintf("编号%d完成",index)
	ch <- s

	wg.Done()
}

func main() {
	ch := make(chan string, 10)
	for i := 0; i < 4; i++ {
		wg.Add(1)
		go request(i, ch)
	}

	go func() {
		wg.Wait()
		close(ch)
	}()

	LOOP:
		for {
			select {
			case i,ok := <-ch: // select会一直等待,直到某个case的通信操作完成时,就会执行case分支对应的语句
        if !ok {
          break LOOP
        }
				println(i)
			default:
				time.Sleep(time.Second)
				fmt.Println("无数据")
			}
		}
}

2.4 案例四:给满了的channel发送数据

func main() {
	ch := make(chan struct{}, 3)

	for i := 0; i < 4; i++ {
		ch <- struct{}{}
	}
}

发生原因:

ch 是一个带缓冲的channel,但是只能缓冲三个struct,当channel满了过后,继续往channel发送数据会阻塞,但是无法解除阻塞,发生死锁。

解决办法:读取channel中的数据

package main

import (
	"fmt"
	"sync"
	"time"
)

var wg sync.WaitGroup

func main() {
	ch := make(chan struct{}, 3)
	
	go func() {

		for {
			select {
			case i, ok := <- ch:
				wg.Done()
				fmt.Println(i)
				if !ok {
					return
				}
			}
		}
	}()

	for i := 0; i < 4; i++ {
		wg.Add(1)
		ch <- struct{}{}
	}

	wg.Wait()
}

3、总结

最重要的是记住golang中死锁的触发条件:当 goroutine 发生阻塞,但是无法解除阻塞状态时,就会发生死锁。然后在使用或者阅读代码时,再根据具体情况进行分析。

channel异常情况总结:

 

 

注意:对已经关闭的channel再次关闭,也会发生panic

标签:ch,思考,阻塞,golang,死锁,func,main,channel
From: https://www.cnblogs.com/zhanchenjin/p/18404392

相关文章

  • 【编程底层思考】理解控制反转Inverse of Control,IOC 和 依赖注入Dependency Injecti
    RodJohnson是第一个高度重视以配置文件来管理Java实例的协作关系的人,他给这种方式起了一个名字:控制反转(InverseofControl,IOC)。后来MartineFowler为这种方式起了另一个名称:依赖注入(DependencyInjection),因此不管是依赖注入,还是控制反转,其含义完全相同。当某个Java对象(......
  • 【Golang】LeetCode面试经典150题:45. 跳跃游戏 II
    题干:给定一个长度为 n 的 0索引整数数组 nums。初始位置为 nums[0]。每个元素 nums[i] 表示从索引 i 向前跳转的最大长度。换句话说,如果你在 nums[i] 处,你可以跳转到任意 nums[i+j] 处:0<=j<=nums[i] i+j<n返回到达 nums[n-1] 的最小跳跃次数......
  • Java多线程中常见死锁问题及解决方案
    在编写Java多线程代码的时候,很难避免会出现线程安全问题,在线程安全问题中也有一个很常见的现象就是死锁现象。今天我们就来聊一聊Java中的死锁问题,以及如何避免死锁问题。本次知识点讲解建立在大家已经知道“锁”......
  • 【Linux修行路】线程安全和死锁
    目录⛳️推荐一、线程安全1.1常见的线程不安全情况1.2常见的线程安全情况1.3常见的不可重入情况1.4常见可重入的情况1.5可重入与线程安全的联系1.6可重入与线程安全的区别二、死锁2.1死锁的四个必要条件2.2如何避免产生死锁?⛳️推荐前些天发现了一个巨牛......
  • SQLServer 如何收集数据以排除 SQL 死锁问题
     方案一使用SQLProfiler跟踪工具捕获死锁数据:1.登录SQLServerManagementStudio2.单击工具、SQLServerProfiler,然后进行身份验证3.单击"事件选择"选项。4.取消选择所有选项。5.单击以下两个选项: ·显示所有列 ·显示所有事件框6.展开锁。7.选择以下内容: ·死锁图 ·锁......
  • 是时候重新思考你的Google广告策略了吗?
    以产品为中心、仅以关键词为焦点的广告活动是谷歌广告中常见的一种活动类型。如果你销售复古女式T恤,你可能会设置基于“复古女式T恤”关键词的独立关键词广告活动。对于许多B2C零售商来说,这种方法效果不错。但是,对于其他一些商家来说,基于产品和关键词的广告活动并不一定是最......
  • 高中数学题的一些背景思考 1 —— 裴蜀定理
    1裴蜀定理「\(\in\)数论」题目设集合\(M=\left\{7m+5n\left|m,n\in\Z\right.\right\},N=\left\{3m-2n\left|m,n\in\Z\right.\right\}\)。试判断集合\(M,N\)的关系。从gcd和Euclid说起比方说我要求\(\gcd(a,b)\),不妨\(a>b\)。令\(r_0=b\),\[\begin{align......
  • 高中数学题的一些背景思考 2 —— Chebyshev 多项式
    Chebyshev多项式「\({\in}\)代数」这个家伙十分重要!可以牵扯出一堆相关的东西。题目1已知\(a,b,c\in\R,\forallx\in[-1,1]\),都有\(\left|ax^2+bx+c\right|\le1\),则当\(x\in[-1,1]\)时,函数\(f(x)=\left|\left(ax^2+bx+c\right)\left(cx^2+bx+a\right)\right|\)的最......
  • golang实现ip地址扫描
    Golang实现IP地址扫描原创 GoOfficialBlog GoOfficialBlog  2024年09月05日18:13 中国香港 听全文你是否想过哪些设备连接到了家里的Wi-Fi网络?无论是出于安全目的还是单纯的好奇心,我们都可以去了解一下家庭网络中的设备情况。在本文中,我们将介绍如何使用......
  • 【转载】golang内存分配
     同时Go对于GC后回收的内存页,并不是马上归还给操作系统,而是会延迟归还,用于满足未来的内存需求.  在1.10以前go的堆地址空间是线性连续扩展的,比如在1.10(linuxamd64)中,最大可扩展到512GB.因为go在gc的时候会根据拿到的指针地址来判断是否位于......