fatal error: all goroutines are asleep - deadlock

article/2025/10/31 0:35:46

如题,近两天遇到此类错误,发现goroutine以及channel的基础仍需巩固。由该错误牵引出go相关并发操作的问题,下面做一些简单的tips操作和记录。

func hello() {fmt.Println("Hello Goroutine!")
}
func main() {go hello() // 启动另外一个goroutine去执行hello函数fmt.Println("main goroutine done!")
}

1、在程序启动时,Go程序就会为main()函数创建一个默认的goroutine。当main()函数返回的时候该goroutine就结束了,所有在main()函数中启动的goroutine会一同结束!

所以引出sync.WaitGroup的使用。通过它,可以实现goroutine的同步。

var wg sync.WaitGroupfunc hello(i int) {defer wg.Done() // goroutine结束就登记-1fmt.Println("Hello Goroutine!", i)
}
func main() {for i := 0; i < 10; i++ {wg.Add(1) // 启动一个goroutine就登记+1go hello(i)}wg.Wait() // 等待所有登记的goroutine都结束
}

2、单纯地将函数并发执行是没有意义的。函数与函数间需要交换数据才能体现并发执行函数的意义。如果说goroutine是Go程序并发的执行体,channel就是它们之间的连接。channel是可以让一个goroutine发送特定值到另一个goroutine的通信机制。Go 语言中的通道(channel)是一种特殊的类型。通道像一个传送带或者队列,总是遵循先入先出(First In First Out)的规则,保证收发数据的顺序。每一个通道都是一个具体类型的导管,也就是声明channel的时候需要为其指定元素类型。

通道有发送(send)、接收(receive)和关闭(close)三种操作。

发送和接收都使用<-符号。我们通过调用内置的close函数来关闭通道。

关闭后的通道有以下特点:

  1. 对一个关闭的通道再发送值就会导致panic。
  2. 对一个关闭的通道进行接收会一直获取值直到通道为空。
  3. 对一个关闭的并且没有值的通道执行接收操作会得到对应类型的零值。
  4. 关闭一个已经关闭的通道会导致panic。

无缓冲的通道又称为阻塞的通道:

func main() {ch := make(chan int)ch <- 10fmt.Println("发送成功")
}

上面这段代码能够通过编译,但是执行的时候会出现以下错误:

fatal error: all goroutines are asleep - deadlock!goroutine 1 [chan send]:
main.main()

们使用ch := make(chan int)创建的是无缓冲的通道,无缓冲的通道只有在有人接收值的时候才能发送值。

上面的代码会阻塞在ch <- 10这一行代码形成死锁,那如何解决这个问题呢?

一种方法是启用一个goroutine去接收值,并一种方式是使用带缓冲的通道,例如:

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

但是注意:channel 通道增加缓存区后,可将数据暂存到缓冲区,而不需要接收端同时接收 (缓冲区如果超出大小同样会造成死锁)

 channel常见的异常总结,如下图:

 如图,总结,可以看出,产生阻塞的方式,主要容易踩坑的有两种:空的通道一直接收会阻塞;满的通道一直发送也会阻塞!

3、那么,如何解决阻塞死锁问题呢?

1)、如果是上面的无缓冲通道,使用再起一个协程的方式,可使得接收端和发送端并行执行。

2)、可以初始化时就给channel增加缓冲区,也就是使用有缓冲的通道

3)、易踩坑点,针对有缓冲的通道,产生阻塞,如何解决?

如下面例子,开启多个goroutine并发执行任务,并将数据存入管道channel,后续读取数据:

package mainimport ("fmt""sync""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{fmt.Println(len(ch))fmt.Println(ret)}
}

错误如下: 

不可靠的解决方式如下:

	for {i, ok := <-ch // 通道关闭后再取值ok=false;通道为空去接收,会发生阻塞死锁if !ok {break}println(i)}
for ret := range ch{fmt.Println(len(ch))fmt.Println(ret) //通道为空去接收,会发生阻塞死锁}

以上两种从通道获取方式,都有小坑! 一旦获取的通道没有主动close(ch)关闭,而且通道为空时,无论通过for还是foreach方式去取值获取,都会产生阻塞死锁deadlock chan receive错误! 

可靠的解决方式1 如下:

package mainimport ("fmt""sync""time"
)var wg sync.WaitGroupfunc request(index int,ch chan<- string)  {time.Sleep(time.Duration(index)*time.Second)s := fmt.Sprintf("编号%d完成",index)ch <- sdefer wg.Done()
}func main() {ch := make(chan string, 10)go func() {wg.Wait()close(ch)}()for i := 0; i < 4; i++ {wg.Add(1)go request(i, ch)}for ret := range ch{fmt.Println(len(ch))fmt.Println(ret)}
}

解决方式: 即我们在生成完4个goroutine后对data channel进行关闭,这样通过for range从通道循环取出全部值,通道关闭就会退出for range循环。

具体实现:可以利用sync.WaitGroup解决,在所有的 data channel 的输入处理之前,wg.Wait()这个goroutine会处于等待状态(wg.Wait()源码就是for循环)。当执行方法处理完后(wg.Done),wg.Wait()就会放开执行,执行后面的close(ch)。

可靠的解决方式2 如下:

package mainimport ("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)for i := 0; i < 4; i++ {go request(i, ch)}for {select {case i := <-ch: // select会一直等待,直到某个case的通信操作完成时,就会执行case分支对应的语句println(i)default:time.Sleep(time.Second)fmt.Println("无数据")}}
}	

 上面这种方式获取,通过select case + default的方式也可以完美避免阻塞死锁报错!但是适用于通道不关闭,需要时刻循环执行数据并且处理的情境下。

4、由此,引入了select多路复用的使用

在某些场景下我们需要同时从多个通道接收数据。通道在接收数据时,如果没有数据可以接收将会发生阻塞。select的使用类似于switch语句,它有一系列case分支和一个默认的分支。每个case会对应一个通道的通信(接收或发送)过程。select会一直等待,直到某个case的通信操作完成时,就会执行case分支对应的语句。具体格式如下:

select{case <-ch1:...case data := <-ch2:...case ch3<-data:...default:默认操作
}

一定留意,default的作用很大! 是避免阻塞的核心。

使用select语句能提高代码的可读性。

  • 可处理一个或多个channel的发送/接收操作。
  • 如果多个case同时满足,select会随机选择一个。
  • 对于没有caseselect{}会一直等待,可用于阻塞main函数。

5、实际项目中goroutine+channel+select的使用

如下,使用于 项目监听终端中断信号操作

srv := http.Server{Addr:    setting.AppConf.Http.Addr,Handler: routers.SetupRouter(setting.AppConf),}go func() {// 开启一个goroutine启动服务if err := srv.ListenAndServe(); err != nil {zap.S().Errorf("listen finish err: %s addr: %s", err, setting.AppConf.Http.Addr)}}()// 等待中断信号来优雅地关闭服务器,为关闭服务器操作设置一个5秒的超时sig := make(chan os.Signal, 1)signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)for {select {case s := <-sig:zap.S().Infof("recv exit signal: %s", s.String())ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)defer cancel()// 5秒内优雅关闭服务(将未处理完的请求处理完再关闭服务),超过5秒就超时退出if err := srv.Shutdown(ctx); err != nil {zap.S().Fatal("Server Shutdown err: ", err)}zap.S().Info("Server Shutdown Success")return}}

 如下,使用于 项目通过通道来进行数据处理、数据发送接收等操作

package taillog// 专门从日志文件,收集日志
import ("context""fmt""github.com/hpcloud/tail""logagent/kafka"
)
//var (
//	tailObj *tail.Tail
//)//TailTask 一个日志收集的任务
type TailTask struct {path stringtopic stringinstance *tail.Tail//为了能实现退出t.runctx context.ContextcancelFunc context.CancelFunc
}func NewTailTask(path,topic string) (tailObj *TailTask)  {ctx,cancel := context.WithCancel(context.Background())tailObj = &TailTask{path:path,topic:topic,ctx:ctx,cancelFunc:cancel,}tailObj.init() //根据路径去打开对应的日志return
}func (t *TailTask)init()  {config := tail.Config{ReOpen:    true, //重新打开Follow:    true, //是否跟随Location:  &tail.SeekInfo{Offset:0,Whence:2}, //从文件哪个地方开始读MustExist: false, //文件不存在不报错Poll:      true,}var err errort.instance, err = tail.TailFile(t.path, config)if err != nil {fmt.Println("tail file failed,err:",err)}// 当goroutine执行的函数退出的时候,goroutine结束go t.run() //直接去采集日志,发送到kafka
}func (t *TailTask)run()  {for{select {case <- t.ctx.Done():fmt.Printf("tail task:%s_%s 结束了\n",t.path,t.topic)returncase line := <- t.instance.Lines: //从tailObj一行行读取数据//发往kafka//kafka.SendToKafka(t.topic,line.Text) //函数调用函数// 优化,先把日志数据发送到一个通道中// kafka包中有单独的goroutine去取日志发送到kafkakafka.SendToChan(t.topic,line.Text)}}
}

package kafka//专门从kafka写日志
import ("fmt""github.com/Shopify/sarama""time"
)type logData struct {topic stringdata string
}var (client sarama.SyncProducer //声明一个全局连接kafka的生产者clientlogDataChan chan *logData
)// 初始化client
func Init(address []string, maxSize int)(err error) {config := sarama.NewConfig()config.Producer.RequiredAcks = sarama.WaitForAll //发送完数据需要leader和follow都确认config.Producer.Partitioner = sarama.NewRandomPartitioner //新选出一个partitionconfig.Producer.Return.Successes = true //成功交付的消息将在success channel 返回//连接kafkaclient,err = sarama.NewSyncProducer(address,config)if err != nil {fmt.Println("producer closed,err:",err)return}// 初始化logDataChanlogDataChan = make(chan *logData,maxSize)// 开启后台的goroutine从通道取数据,发送kafkago sendToKafka()return
}// 给外部暴漏一个函数,该函数只把日志数据发送到一个内部chan中
func SendToChan(topic,data string)  {msg := &logData{topic: topic,data:  data,}logDataChan <- msg
}//真正往kafka发送日志的函数
func sendToKafka()  {for{select {case ld := <- logDataChan:// 构造一个消息msg := &sarama.ProducerMessage{}msg.Topic = ld.topicmsg.Value = sarama.StringEncoder(ld.data)// 发送到kafkapid,offset,err := client.SendMessage(msg)if err != nil {fmt.Println("send msg failed,err:",err)return}fmt.Printf("pid:%v,offset:%v\n",pid,offset)default:time.Sleep(time.Microsecond*50)}}
}

整理比较随性,有点混乱,后续如果再碰到坑继续整理,继续踩坑优化~ 


http://chatgpt.dhexx.cn/article/8qGMQAPY.shtml

相关文章

死锁问题(Deadlock)

4.8 死锁问题(Deadlock) 各进程在使用系统资源时&#xff0c;应注意系统产生死锁问题。下面先介绍什么是死锁。 4.8.1 死锁的概念  1. 死锁的定义 所谓死锁&#xff0c;是指各并发进程彼此互相等待对方所拥有的资源&#xff0c;且这些并发进程在得到对方的资源之前不会释…

SQL Server DeadLock 分析

1. 设置 XEvents 会话以收集死锁 1.1 扩展事件-会话-右键新建会话向导 1.2 设置会话名称 1.3 选择要捕获的事件 database_xml_deadlock_report 1.4 捕获全局字段 1.5 指定会话存储 1.6 结束 1.7 确保启动会话 2. 制造死锁 2.1 准备数据表及数据 2.1.1 准备数据库 TestBu…

MYSQL报错:MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting tr

mysql报错&#xff1a;MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction 报错情况&#xff1a;并发量大的情况对表的修改发生死锁 原因&#xff1a;查看了该更新语句的sql&#xff0c;没有设置主键&#xff0c;而mysq…

Deadlock的一些总结(死锁分析及处理)

1.1.1 摘要 在系统设计过程中&#xff0c;系统的稳定性、响应速度和读写速度至关重要&#xff0c;就像12306.cn那样&#xff0c;当然我们可以通过提高系统并发能力来提高系统性能总体性能&#xff0c;但在并发作用下也会出现一些问题&#xff0c;例如死锁。 今天的博文将着重介…

Deadlock found when trying to get lock; try restarting transaction 【MySQL死锁问题解决】

视频地址&#xff1a; https://www.bilibili.com/video/bv1zY411N7tB 最近在调试接口的时候遇到了MySQL死锁问题&#xff0c;我自己测试的时候一切都好好的&#xff0c;但在并发下&#xff0c;就死锁了 其实死锁问题&#xff0c;并没有一个类似“万金油”的解决办法&#xff0…

【死锁~】

死锁 死锁 死锁 1.死锁是什么? 当线程想要获取锁&#xff0c;但是获取失败&#xff0c;此时&#xff0c;线程进入阻塞状态&#xff0c;等待锁释放之后&#xff0c;线程获取锁。如果锁一直没有被释放&#xff0c;线程就一直处于阻塞状态。 2.死锁的条件 1.互斥作用:一个线程获…

Mysql报Deadlock found when trying to get lock; try restarting transaction问题解决

Mysql报Deadlock found when trying to get lock; try restarting transaction问题解决!! 文章目录 问题发生场景Mysql锁类型分析死锁原理问题排查过程问题原因解决方法经验教训查看mysql死锁日志 问题发生场景 今天记录一下最近项目中遇到的一个问题,前几天在部署项目后,在线…

死锁(Deadlock)

什么是死锁 死锁是指两个或两个以上的线程在执行过程中&#xff0c;因争夺资源而造成的一种互相等待的现象。若无外力作用&#xff0c;它们都将无法推进下去。 产生死锁的四个必要条件得烂熟于心 互斥条件&#xff1a;进程要求对所分配的资源进行排他性控制&#xff0c;即在一段…

知识归纳:死锁

一. 死锁定义 死锁(Deadlock)是指两个或两个以上的进程在执行过程中&#xff0c;由于竞争资源或者由于彼此通信而造成的一种阻塞的现象&#xff0c;若无外力作用&#xff0c;它们都将无法推进下去。 举例:毕业生找工作,公司表示只需要有工作经验才可以来;要想有工作经验,就需要…

mmall数据库学习笔记

mmall数据库学习笔记 文章目录 mmall数据库学习笔记唯一索引产品表购物车表支付信息表订单表订单明细表收货地址表外键 唯一索引 在用户表中&#xff0c;设置了用户名作为唯一索引&#xff0c;理由如下&#xff1a;用户名是不允许重复的&#xff0c;那么当不是分布式开发的时候…

MMALL ADMIN 后台管理总结

后台管理项目前期准备 1&#xff0c;vue-cli2 项目框架 2&#xff0c;下载axios插件 cnpm install axios&#xff0c;安装Element.ui &#xff0c; vue-cli2中使用scss 注意版本 cnpm install sass-loader7.1.0 --save-dev &#xff08;8.0.0&#xff09; v…

mmall项目安装相关包文件

2019独角兽企业重金招聘Python工程师标准>>> 本步骤之前请先&#xff1a;建立相关文件夹&#xff08;或者从git上clone项目下来&#xff09;&#xff0c;进入分支作业目录下运行个忠包文件的安装。注意&#xff1a;npm是所有安装的第一步 基础安装&#xff1a; 1.np…

mmall电商项目学习笔记之 idea,maven工程整合ssm框架

项目目录结构 1.pom文件导入jar包 1.1 <properties><!--设置编码格式--><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding><!--sprin…

Demo_mmall v2.0 (四) Tomcat集群演进及使用Redis进行session重构实现单点登录

小谈mmall架构演进 上回书和上上回书说到redis的用法还有在代码里怎么操作Redis数据库&#xff0c;学完了得用啊。怎么用啊&#xff1f;这得从项目架构说起了。 mmall是一个简单的用SSM搭建起来的基本只能本地玩耍的电商DEMO&#xff0c;最简单的架构版本V1.0是这样婶的&#…

自学实践前后端项目4 MMall商城 2

一。搭建静态页面 1)UserController里面实现登录操作 Autowired private UserService userService;PostMapping("/login") public String login(String loginName, String password, HttpSession session){QueryWrapper wrapper new QueryWrapper();wrapper.eq(&q…

mmall用户模块

mmall用户模块 user数据表设计用户模块接口文档服务端响应对象&#xff08;ServerResponse< T>&#xff09;响应对象封装以下3个属性判断响应是否成功私有化构造函数&#xff0c;对外暴露静态方法返回所需要的响应对象&#xff0c;例如&#xff1a;响应成功响应失败 Resp…

自学实践前后端项目4 MMall商城 7

一。地址管理 1.前端改为 userAddress 2. OrderController增加两个需要的元素 3.接口 服务也加上去 4. 在OrderServiceImpl实现层判断是否为新地址再进行保存 //先判断新老地址 if (orders.getUserAddress().equals("newAddress")){//存入数据库UserAddress use…

自学实前后端践项目4 MMall商城 1

一.开发环境 1.JDK8以上Spring Boot 2.3.0ThymeleafMyBatis Plus3.3.1MySQL8.0 2.部署&#xff1a;Linux,&#xff0c;&#xff08;阿里云 腾讯云&#xff09;JDK8&#xff0c;MySQL8.0 3.部署方式&#xff1a;jar包部署&#xff0c;不需要Tomcat 二.新建工程 1&#xff0…

mmall电商项目学习笔记之mybatis三剑客

一.Mybatis plugin IDEA 2017.3版本下Mybatis plugin 3.53安装使用 插件下载地址 http://www.awei.org/download/iMybatis-3.21.jar 二.MyBatis-Generate 反向生成 【转】mybatis自动生成实体代码的插件 【method2】逆向生成 2.1 在pom.xml中做两处配置 2.1.1配置depen…

自学实践前后端项目4 MMall商城 4

一。实现商品详情展示 1.测试获取后台当个商品的信息 2.实现通过点击商品名称和商品图片进入商品详情页面 1&#xff09;查找出商品信息 2&#xff09;在前端进行映射 3&#xff09;设置库存选择限制 判断逻辑 $(function(){//给type绑定点击事件$(".type").click…