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

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

时间:2023-03-18 17:55:34浏览次数:40  
标签:ch 阻塞 golang 死锁 线程 思考 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 被阻塞而无法解除阻塞时产生的一种状态。

我理解的无法解除阻塞是:程序无法继续执行。

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,死锁,线程,思考,main,channel
From: https://www.cnblogs.com/huageyiyangdewo/p/17231337.html

相关文章

  • 死锁
    packageedu.wtbu;//死锁:多个线程互相抱着对方需要的资源,然后形成僵持publicclassDemo10{publicstaticvoidmain(String[]args){Makeupg1=newMake......
  • Linux进程调度的思考
    a.进程怎么载入到CPU中运行?b.当前进程正在运行,怎么确定是否要切换下一个进程?c.怎么选出下一个进程?d.选择进程的算法和优先级?e.介绍一下常用的算法调度逻辑?f.从......
  • golang使用缓存库go-cache的测试用例-短期内存缓存数据类似memcache/redis-【唯一客服
    golang中使用go-cache是非常普遍的,比如,我在对接微信客服接口的时候,获取access_token,默认获取一次有两个小时的有效期这个时候,我就可以使用go-cache来缓存access_token了......
  • golang介绍和环境搭建
    一、Goland语言介绍Go和C语言、C++、Python、Java等一样都是编程语言。学习任何一门编程语言本质上都分3步走:第一步:安装解释器或编译器。第二步:学相关编程语言语......
  • golang开发之旅
    一、golang基础golang介绍和环境搭建二、Docker相关......
  • 个人所最偏爱的“游戏的表达”的思考和总结
    思维导图,个人比较喜欢用MindMaster这个思维导图软件,所以会使用这个软件,来整理思绪。《游戏改变世界》中一些个人最为关注的观点个人的话,还是更喜欢,童话,卡通,童真类型的,......
  • golang代码覆盖率测试
    1.概述测试是开发过程的重要部分,也是软件开发生命周期的关键部分。它可以确保应用程序正常运行和满足客户需求。本文将涵盖关于Go测试的所有须知事项。我们将从一个......
  • 代码去重引发对lamba表达式思考
    对重复代码进行治理,发现很多冗余代码就因为其中的一行无法复用,就拷贝一份,造成大量重复代码,且后期维护成本很高,很容易改漏了 1、使用函数指针进行代码去重如下代码相似......
  • 带参数的微信小程序码服务端(golang)生成
    官方接口微信小程序码共有3个接口可以生成:获取小程序码POSThttps://api.weixin.qq.com/wxa/getwxacode?access_token=ACCESS_TOKEN该接口用于获取小程序码,适用于需要......
  • Goravel ORM 新增模型关联,用 Golang 写关联也可以跟 Laravel 一样简单
    关于GoravelGoravel是一个功能完备、具有良好扩展能力的Web应用程序框架。作为一个起始脚手架帮助Golang开发者快速构建自己的应用。框架风格与Laravel保持一致,让......