浅谈GO中的Channel以及死锁的造成


Posted in Golang onMarch 18, 2022

写在前面

这篇文章的诞生要感谢MIT 6.284课程。在其中一节课中,谈到了多线程的协同的一些问题,其中就涉及到了channel这个概念,并由一段代码引发思考并逐渐深入得到了这篇文章。

引子

课程中有一段代码如下:

浅谈GO中的Channel以及死锁的造成

其大致含义是:代码背景是在进行多线程网络爬虫页面url,master线程启动后,从channel通道中读取当前页面的所有url即urls,接着再对这个urls中的每一个url进行爬虫读取新页面中的urls(即执行go worker(u, ch ,fetcher)),每启动一个worker线程便开始向channel中写入该url指向页面中所有包含的urls,以供master线程读取。

问题抛出

那么问题来了,为什么第一层for循环不会range完ch之后便直接结束循环,还需要利用局部变量n来根据特定情况跳出循环?

问题解释

课程上的解释是,这个range会一直阻塞,但并未提出解释。其实,这里很容易分析,因为当前的channel是一个无缓冲通道。所谓无缓冲通道,简单的讲就是两个线程对channel进行操作,一个读,一个写,永远都只能是写一个,读一个按照这样的顺序进行。更详细一些的话,读的那个线程会一直阻塞,直到写的线程向channel中写入一个数据。反之亦然,写的线程在完成一次写操作之后,也会一直阻塞直到另外一个线程完成对该channel的读取操作。上述情况只有一种例外状况,那就是该channel通道被某个线程close掉了:close(channel)。

而这里的range其实不太等同于对数组的range,这里的range实质上为对channel通道的读取。所以,在并未有认为close通道的前提下,该for循环会一直阻塞,不会退出,于是需要设定一个局部状态量n让其退出循环,保证程序的正常运行。当然我们也可以通过close其channel来实现,不过我认为close的时机可能不是非常容易把握。

继续深入

完成上述思考之后,对channel进行了较为的深入的分析,当然分析是以具体的实验展开的。给出下述实验代码:

func main() {
    test()
}

func test()  {
	ch := make(chan int,4)

	go func() {
		ch <- 1
		ch <- 2
		ch <- 3
		ch <- 4
	}()

	//go func() {
	for a := range ch {
		fmt.Print(a)
	}
	//}()

	fmt.Print("test is over")
}

执行结果直接报错,显示:fatal error: all goroutines are asleep - deadlock!
即:出现死锁。
为什么会出现这种情况?
首先我们来分析一下这段代码的目的:利用channel通道,实现数据的传递,一个线程向channel通道中写入数据,另外一个读取。为什么会出现死锁呢?

首先我们分析一下当前程序有多少个线程在执行,main函数是主线程,调用test函数之后,主线程进入了test函数中继续运行。而在test函数中,采用闭包函数或者说匿名函数的方法新开了一个线程,即goroutine去向已经生成的无缓冲通道中发送数据。发送的过程并非是主线程的任务,所以主线程在执行完go func之后马上跳过继续执行下面的for循环,也就是要将channel中的数据读取出来。

for a := range ch {
		fmt.Print(a)
	}

这时,问题来了。现在两个线程,主线程读,另外一个写。在另外一个线程完成最后一个写之后,主线程开始阻塞等待新的写操作,而主线程一旦阻塞整个test函数也无法结束,所以导致了死锁的产生,主线程一直被阻塞。

明白了上述原因之后,解决方法便很简单了,将从channel中读数据的任务交给另外一个线程,而非主线程,主线程直接调用完test函数之后马上结束,其他两个线程的死活都不会影响到程序本身的运行,即主线程的运行。如下:

func main() {
    test()
}

func test()  {
	ch := make(chan int,4)

	go func() {
		ch <- 1
		ch <- 2
		ch <- 3
		ch <- 4
	}()

	go func() {
	for a := range ch {
		fmt.Print(a)
	}
	}()

	fmt.Print("test is over")
}

当然这种方法是偷懒的,这样的操作有可能导致内存溢出等情况发生,所以最好还是让发送数据的线程在发送完之后将channel关闭,如下所示:

func main() {
    test()
    time.Sleep(time.Second)
}

func test()  {
	ch := make(chan int,4)

	go func() {
		ch <- 1
		ch <- 2
		ch <- 3
		ch <- 4
		close(ch)
	}()

	go func() {
	for a := range ch {
		fmt.Print(a)
	}
	}()

	fmt.Print("test is over")
}

输出为:

test is over1234

注意,这里为了保证能够输出1234,需要将主线程休眠1s,确保主线程在退出之前,负责读取的线程能够完成读取工作。

写在后面

Go语言对多线程天然的集成性,让其在处理并发的一些事务时十分方便,但是还是需要注意一些死锁的生成。

到此这篇关于浅谈GO中的Channel以及死锁的造成的文章就介绍到这了,更多相关GO中Channel及死锁内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Golang 相关文章推荐
go原生库的中bytes.Buffer用法
Apr 25 Golang
golang中切片copy复制和等号复制的区别介绍
Apr 27 Golang
基于Go Int转string几种方式性能测试
Apr 28 Golang
解决golang post文件时Content-Type出现的问题
May 02 Golang
Golang 获取文件md5校验的方法以及效率对比
May 08 Golang
入门学习Go的基本语法
Jul 07 Golang
go goroutine 怎样进行错误处理
Jul 16 Golang
Go语言实现Base64、Base58编码与解码
Jul 26 Golang
Golang获取List列表元素的四种方式
Apr 20 Golang
详解Go语言中Get/Post请求测试
Jun 01 Golang
GoFrame基于性能测试得知grpool使用场景
Jun 21 Golang
Go本地测试解耦任务拆解及沟通详解Go本地测试的思路沟通的重要性总结
Jun 21 Golang
Golang 并发下的问题定位及解决方案
Mar 16 #Golang
如何利用golang运用mysql数据库
深入理解go缓存库freecache的使用
Feb 15 #Golang
Go语言读取txt文档的操作方法
Jan 22 #Golang
一文搞懂Golang 时间和日期相关函数
Go语言基础切片的创建及初始化示例详解
Nov 17 #Golang
Go语言基础map用法及示例详解
Nov 17 #Golang
You might like
php生成文件
2007/01/15 PHP
模拟flock实现文件锁定
2007/02/14 PHP
PHP 程序员也要学会使用“异常”
2009/06/16 PHP
服务器web工具 php环境下
2010/12/29 PHP
针对多用户实现头像上传功能PHP代码 适用于登陆页面制作
2016/08/17 PHP
thinkphp框架类库扩展操作示例
2019/11/26 PHP
Javascript 中介者模式实例
2009/12/16 Javascript
Visual Studio中的jQuery智能提示设置方法
2010/03/27 Javascript
文本框水印提示效果的简单实现代码
2014/02/22 Javascript
什么是JavaScript中的结果值?
2016/10/08 Javascript
vue router2.0二级路由的简单使用
2017/07/05 Javascript
nodejs中安装ghost出错的原因及解决方法
2017/10/23 NodeJs
vue addRoutes实现动态权限路由菜单的示例
2018/05/15 Javascript
vue自定义底部导航栏Tabbar的实现代码
2018/09/03 Javascript
Webstorm2016使用技巧(SVN插件使用)
2018/10/29 Javascript
详解vue中async-await的使用误区
2018/12/05 Javascript
javascript面向对象三大特征之多态实例详解
2019/07/24 Javascript
基于openlayers实现角度测量功能
2020/09/28 Javascript
Python决策树和随机森林算法实例详解
2018/01/30 Python
python语言基本语句用法总结
2019/06/11 Python
Python3远程监控程序的实现方法
2019/07/15 Python
在django admin中添加自定义视图的例子
2019/07/26 Python
解决Django加载静态资源失败的问题
2019/07/28 Python
Python根据URL地址下载文件并保存至对应目录的实现
2020/11/15 Python
HTML5+WebSocket实现多文件同时上传的实例
2016/12/29 HTML / CSS
Auguste The Label官网:澳大利亚一家精品女装时尚品牌
2020/06/14 全球购物
党支部公开承诺践诺书
2014/03/28 职场文书
社区健康教育工作方案
2014/06/03 职场文书
国际商贸专业自荐信
2014/06/09 职场文书
乡文化站暑期培训方案
2014/08/28 职场文书
投标承诺函格式
2015/01/21 职场文书
2015年煤矿安全工作总结
2015/05/23 职场文书
贫困证明书范文
2015/06/16 职场文书
高中历史教学反思
2016/02/19 职场文书
重温经典:乔布斯在斯坦福大学的毕业演讲(双语)
2019/08/26 职场文书
详解Vue3使用axios的配置教程
2022/04/29 Vue.js