Golang程序报错:fatal error: all goroutines are asleep - deadlock

news2025/1/25 4:27:12

文章目录

  • 1.原始代码
  • 2.错误原因分析
  • 3. 解决方案
  • 4. 经验总结
  • 5. 练习

完整的报错信息如下:

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan receive]:
main.(*WorkerManager).KeepAlive(0xc000088f60)
        /root/go_workspace/studygoup/05.go:66 +0x59
main.(*WorkerManager).StartWorkPool(0xc000088f60)
        /root/go_workspace/studygoup/05.go:61 +0xef
main.main()
        /root/go_workspace/studygoup/05.go:88 +0x47

goroutine 18 [chan receive]:
main.(*Worker).Work(0x0?, 0x0?, 0x0?)
        /root/go_workspace/studygoup/05.go:34 +0xfb
created by main.(*WorkerManager).StartWorkPool
        /root/go_workspace/studygoup/05.go:58 +0x27

当然这里的文件路径跟你不一样。主要就是fatal error: all goroutines are asleep - deadlock!以及其中的chan receive.

1.原始代码

我是在构建发送任务与任务处理搭配协程池的时候所遇到的。代码架构如下:请添加图片描述
一开始就会生成这样一个workerPool,然后在执行任务的时候,goroutine挂掉了,就会被放进Wokers中,经过检查会被重新放回进WorkerPool中。
完整的代码逻辑如下:

package main

import (
	"fmt"
	"math"
	"sync"
)

// 创建一个无缓冲的channel
var workChan = make(chan int, 0)

type Worker struct {
	id  int
	err error
}

func (w *Worker) Work(c chan int, workers chan *Worker) {
	defer func() {
		wg.Done()
		// 捕获错误
		if r := recover(); r != nil {
			// 将错误断言
			if err, ok := r.(error); ok {
				w.err = err
			} else {
				w.err = fmt.Errorf("panic happened with [%v]", r)
			}
			// 通知主goroutine,将死亡的chan加入
			workers <- w
		}
	}()
	// 模拟业务,这里应该也会阻塞
	for each := range c {
		fmt.Println("Task:", each)
	}
}

type WorkerManager struct {
	Workers  chan *Worker
	nWorkers int
}

func NewWorkerManager(num int) *WorkerManager {
	return &WorkerManager{
		Workers:  make(chan *Worker, num),
		nWorkers: num,
	}
}

func (wm *WorkerManager) StartWorkPool() {
	// 开启指定数量的线程池
	for i := 0; i < wm.nWorkers; i++ {
		// 先创建一个worker对象
		wk := &Worker{id: i}
		go wk.Work(workChan, wm.Workers)
	}
	// 协程保活
	wm.KeepAlive()

}

func (wm *WorkerManager) KeepAlive() {
	// 这里会阻塞
	for wk := range wm.Workers {
		// log the error
		fmt.Printf("Worker %d stopped with err: [%v] \n", wk.id, wk.err)
		// reset err
		wk.err = nil
		// 当前这个wk已经死亡了,需要重新启动他的业务
		go wk.Work(workChan, wm.Workers)
	}
}

// 保证所有资源的顺利执行
var wg = sync.WaitGroup{}

func SendTask(c chan int, task int) {
	wg.Add(1)
	c <- task
}

func main() {
	// 创建线程管理者
	wm := NewWorkerManager(10)
	// 开启指定数量的线程池
	wm.StartWorkPool()
	// 总的任务数
	TaskCnt := math.MaxInt64
	// 依次发送任务
	for i := 0; i < TaskCnt; i++ {
		SendTask(workChan, i)
	}
	// 等待完成
	wg.Wait()
}

一旦按照上面的代码执行,就会立即报错!报错信息如最上面的详细信息所示。

2.错误原因分析

  • main函数中执行wm.StartWorkPool(),注意在这里是串行执行,也就是说要等wm.StartWorkPool()执行完毕才执行下面一句TaskCnt := math.MaxInt64
  • 然后我们深入wm.StartWorkPool()会发现,其中没有明显阻塞的地方,而是调用了一个wm.KeepAlive(),其中就是一直监视NewWorkerManager.Workers中是否有被添加worker,即wm.KeepAlive()中语句for wk := range wm.Workers {...,问题就是出现在这里,程序一直在这里阻塞,无法回到main中继续执行,要在后续的Work中才有可能发生向NewWorkerManager.Workers添加操作。因此直接造成了死锁

3. 解决方案

因为是在wm.StartWorkPool()——> wm.KeepAlive()——>for wk := range wm.Workers {...,最终造成了阻塞,因此我们在这三个中任一个位置开启协程来处理。这里我直接在main函数中使用go wm.StartWorkPool()即可最终解决问题。

func main() {
	// 创建线程管理者
	wm := NewWorkerManager(10)
	// 开启指定数量的线程池
	go wm.StartWorkPool()
	// 总的任务数
	TaskCnt := math.MaxInt64
	// 依次发送任务
	for i := 0; i < TaskCnt; i++ {
		SendTask(workChan, i)
	}
	// 等待完成
	wg.Wait()
}

4. 经验总结

  • 先分析可能发生阻塞的地方;【尤其是管道读取的地方】
  • 从主函数入手,依次分析并理清阻塞处的逻辑执行顺序;
  • 针对一块阻塞处,判断其写操作会不会在其后面,程序永远到不了;
  • 理清调用链逻辑,确定协程开启的地方。【开启协程的地方不会阻塞,立即往下执行】

如果实在还无法执行,将你的代码post到评论区,让大家一起帮你解决!

5. 练习

查看下面哪个地方会发生deadlock。该程序使用协程完成1~30的各个数的阶层,并把各个结果放进map中。最后显示出来

package main

import (
	"fmt"
	"sync"
)



var wg = sync.WaitGroup{}
var mapLock = sync.Mutex{}

func Calculate(nums chan int, resMap map[int]uint64) {

	// 进来会直接阻塞等待
	for num := range nums {
		var res uint64 = 1
		for i := 1; i <= num; i++ {
			res = res * uint64(i)
		}
		// 加锁保证并发的安全
		mapLock.Lock()
		// map并发不安全
		resMap[num] = res
		mapLock.Unlock()

	}
	wg.Done()

}

func SendTask(c chan int, task int) {
	wg.Add(1)
	c <- task
}

// 将发送任务与处理任务分开
func main() {
	// 创建一个无缓冲的管道
	numsChan := make(chan int)
	// 创建一个保存结果的map
	resMap := make(map[int]uint64)
	// 开启指定数量的协程
	for i := 0; i < 3; i++ {
		go Calculate(numsChan, resMap)
	}
	// 循环把各个数据放进管道中
	for i := 1; i <= 30; i++ {
		SendTask(numsChan, i)
	}

	wg.Wait()
	fmt.Println(resMap)
}

答案放在评论区

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/433722.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Doris(7):数据导入(Load)之Routine Load

例行导入功能为用户提供了义中自动从指定数据源进行数据导入的功能 1 适用场景 当前仅支持kafka系统进行例行导入。 2 使用限制 支持无认证的 Kafka 访问&#xff0c;以及通过 SSL 方式认证的 Kafka 集群。支持的消息格式为 csv 文本格式。每一个 message 为一行&#xff0c;…

【Cpp】手撕搜索二叉树(K模型)

文章目录 二叉搜索树概念详解二叉搜索树的概念二叉搜索树的操作(大致思路)二叉搜索树的查找二叉搜索树的插入二叉搜索树的删除(最重点) 手撕搜索二叉树代码结点定义(以key型为例,KV型将在下一篇博客中介绍)树结构定义深拷贝构造函数与构造函数赋值重载析构函数遍历(结果按从小到…

软件测试的当下分析

在没有清晰能见度的情况下驾驶汽车不仅非常危险&#xff0c;也十分鲁莽。这会让我们和我们周边的人随时面临着碰撞、受伤、甚至死亡的风险。如果不能看到前方的道路&#xff0c;我们就无法预测潜在的危险或障碍&#xff0c;从而无法做出明智的决定并采取适当的行动。 同样&…

什么是ddos攻击?ddos攻击有哪些危害?

一、什么是 DDoS 攻击&#xff1f; DDoS 是 Distributed Denial of Service 的缩写&#xff0c;翻译成中文就是 “分布式拒绝服务”。DDoS 攻击将处于不同位置的多个计算机联合起来作为攻击平台&#xff0c;对一个和多个目标发动 DDoS 攻击&#xff0c;从而成倍提高攻击威力。…

分布式系统概念和设计-进程通信中的(网络API设计)

分布式系统概念和设计 进程间通信 中间件层 请求-应答协议 编码和外部数据表示 因特网协议的API 进程间通信的特征 一对进程间进行消息传递需要两个消息通信操作的支持&#xff08;send和receive&#xff09;&#xff0c;用于定义目的地和消息的定义。 为了能够使一个进程能…

煤化工废水除总氮除硬度,矿井水除砷除氟解决方案

随着环保标准的提升&#xff0c;大部分煤矿企业对矿井水要求执行地表三类水标准&#xff0c;氟化物要求小于1mg/l&#xff0c;这类项目存在体量大、氟含量低、水质偏差等特点。 RO工艺制备纯水是煤化工行业生产的一个辅助环节&#xff0c;会产生大量的浓盐水&#xff0c;由于浓…

十五分钟带你学会 Electron

文章目录 什么是 Electron为什么要选择 Electron安装 Electron桌面CSDN实战Electron 基础配置Electron 进程主进程渲染进程主进程与渲染进程的区别主进程与渲染进程的通信 Electron 跨平台问题Electron 部署打包应用程序发布应用程序 Electron 跨端原理总结 什么是 Electron E…

NE555 Motor LED Chaser

文章目录 1.前言2.资料下载 1.前言 这个是从YouTube上搬运来的&#xff0c;如图所示 2.资料下载 所需材料 #1# 10k resistor 1 #2# 10k variable resistor 1 #3# 10uf capacitor 1 #4# 3mm blue led 4 #5# 3mm yellow led 4 #6# 3mm red led 4 #7# 3mm green led 4 #8# 3mm…

【Linux网络】网络基础(TCP/IP协议栈、局域网通信原理、封装与解包、有效载荷分用)

文章目录 1、认识网络1.1 重新看待计算机结构1.2 网络的问题1.3 初识网络协议1.4 TCP/IP五层结构 2、网络与操作系统2.1 网络和OS的关系2.2 局域网&#xff08;以太网&#xff09;通信原理和MAC地址2.3 主机的跨网络2.4 有效载荷的分用 1、认识网络 在早年计算机之间是相互独立…

关于自身存在的严重问题总结_4/19

今早二次面试喜马拉雅&#xff0c;面试官给我的评价是&#xff1a; 1.经验不足&#xff1b; 2.实用方面生疏、理解不到位&#xff1b; 原因很正常&#xff0c;我项目自己亲手实操的太少了&#xff0c;一直在背&#xff0c;但是背 不是去读源码 去理解&#xff1b; 项目也大…

基于springboot的班级综合测评管理系统源码数据库论文

目录 1 绪论 1.1课题研究的背景 1.2 课题研究的内容 1.3 系统开发的意义 1.4初步设计方法与实施方案 1.5 本文研究内容 2相关技术介绍 2.1 Java技术 2.2B/S架构 2.3 MySQL介绍 2.4 Springboot框架 3系统需求分析 3.1 可行性分析 3.1.1 经济可行性分…

AI大模型在各行业肆虐,打工人该如何保住自己的饭碗?

开篇我先下个结论&#xff0c;那就是&#xff1a;人类在科技领域的高效率竞争&#xff0c;正在把我们生活的这个商业世界一步步地数字化。而数字化&#xff0c;不单单是AI智能的发展成果&#xff0c;更会成为它所热衷的生长温床&#xff0c;为后续人工智能的一路狂飙奠定了绝佳…

2、picodet转onnx裁剪及python onnxruntime推理

文章目录 1 对picodet xs1.1 动态图转静态图1.2 静态图转onnx1.3 paddle 含后处理 all 版本的推理1.4 onnx 含后处理 all 进行推理1.5 onnx 不含后处量 base模型推理1.5.1 获取onnx模型任一节点的输出1.5.2 base模型的推理 1.6、对picodet-xs模型进行优化1.6.1 picodet-xs base…

项目文档规范及总体布局

软件文档可以分为开发文档和产品文档两大类&#xff0c;交付用户还有用户文档。 1|1开发文档 软件开发计划需求规格说明书软件概要设计说明数据库设计说明软件详细设计说明可执行程序生成说明软件测试计划软件测试说明软件测试报告安装部署手册源代码交付说明上线部署方案上线…

spark读写时序数据库 TDengine 错误总结

最近在用spark读取、写入TDengine 数据库遇到了这样一个问题&#xff1a; JDBCDriver找不到动态链接库&#xff08;no taos in java.library.path&#xff09; 我本地都好好的&#xff0c;但是一上服务器写入就会报这个错误&#xff0c;看了很久没有排查出问题&#xff0c;后…

图像分割领域的GPT-4.0,分割一切的AI算法:Segment Anything

一、图像分割领域的GPT-4.0 大家好&#xff0c;我是千与千寻&#xff0c;今天给大家介绍的AI算法可以称得上是图像分割领域的GPT-4.0&#xff0c;号称可以分割一切的AI图像分割算法——Segment Anything。 提到GPT-4.0模型&#xff0c;相信不必我多说&#xff0c;大家都不会陌生…

C++笔记——第十三篇 种一颗 AVL树,长大变成 红黑树,开出了 map和set

目录 一、引入 1. 关联式容器 二、键值对 三、树形结构的关联式容器 3.1 set 3.1.1 set的介绍 3.1.2 set的使用 3.2 map3.2.1 map的介绍 3.2.2 map的使用 3.3 multiset 3.3.1 multiset的介绍 3.4 multimap 3.4.1 multimap的介绍 四、底层结构 4.1 AVL 树 4.1.1 AVL树的概念…

实验进行套路【1】

实验是用来证明猜想正确与否的关键方法&#xff0c;做好实验对提升论文发表效率至关重要。本篇博客结合自身经历总结做实验的方法论&#xff0c;希望能对读者有用。本篇不会从技术实现角度来介绍做实验的方法&#xff0c;而会从指导思想角度入手。如果你是一个科研工作者的话&a…

AIPRM for ChatGPT插件让ChatGPT如虎添翼

ChatGPT大热&#xff0c;家人们都申请到了账号&#xff0c;可是总是在一问一答的基础上来完成基本的应用&#xff0c;而不能很好的使用「咒语」&#xff08;Prompt&#xff09;&#xff0c;收获的答案不是通过很多次的反复问获取答案&#xff0c;就是获取的不是想要的答案。所以…

最重要的 JVM 参数总结

1.概述 在本篇文章中&#xff0c;你将掌握最常用的 JVM 参数配置。 2.堆内存相关 Java 虚拟机所管理的内存中最大的一块&#xff0c;Java 堆是所有线程共享的一块内存区域&#xff0c;在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例&#xff0c;几乎所有的对象实…