浅谈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中semaphore(信号量)源码
Apr 03 Golang
go语言中切片与内存复制 memcpy 的实现操作
Apr 27 Golang
go语言中json数据的读取和写出操作
Apr 28 Golang
golang 实现两个结构体复制字段
Apr 28 Golang
golang DNS服务器的简单实现操作
Apr 30 Golang
Go语言 go程释放操作(退出/销毁)
Apr 30 Golang
解决goland 导入项目后import里的包报红问题
May 06 Golang
golang 实现时间戳和时间的转化
May 07 Golang
Golang二维数组的使用方式
May 28 Golang
Go中的条件语句Switch示例详解
Aug 23 Golang
Golang数据类型和相互转换
Apr 12 Golang
Go语言编译原理之源码调试
Aug 05 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之第八天
2006/10/09 PHP
如何提高MYSQL数据库的查询统计速度 select 索引应用
2007/04/11 PHP
phpmailer简单发送邮件的方法(附phpmailer源码下载)
2016/06/13 PHP
php实现的XML操作(读取)封装类完整实例
2017/02/23 PHP
js 页面元素的几个用法总结
2013/11/18 Javascript
带左右箭头图片轮播的JS代码
2013/12/18 Javascript
让JavaScript中setTimeout支持链式操作的方法
2015/06/19 Javascript
jQuery实现可编辑的表格实例讲解(2)
2015/09/17 Javascript
全面解析node 表单的图片上传
2016/11/21 Javascript
jquery实现页面加载效果
2017/02/21 Javascript
nodejs获取微信小程序带参数二维码实现代码
2017/04/12 NodeJs
微信公众平台 客服接口发消息的实现代码(Java接口开发)
2019/04/17 Javascript
jQuery实现图片随机切换、抽奖功能(实例代码)
2019/10/23 jQuery
Webpack设置环境变量的一些误区详解
2019/12/19 Javascript
Vue路由 重定向和别名的区别说明
2020/09/09 Javascript
[54:10]完美世界DOTA2联赛PWL S2 Magma vs FTD 第二场 11.29
2020/12/03 DOTA
通过python+selenium3实现浏览器刷简书文章阅读量
2017/12/26 Python
一个Python最简单的接口自动化框架
2018/01/02 Python
python实现简单银行管理系统
2019/10/25 Python
Matplotlib绘制雷达图和三维图的示例代码
2020/01/07 Python
python代码如何实现余弦相似性计算
2020/02/09 Python
python GUI库图形界面开发之PyQt5简单绘图板实例与代码分析
2020/03/08 Python
scrapy框架携带cookie访问淘宝购物车功能的实现代码
2020/07/07 Python
python开根号实例讲解
2020/08/30 Python
python 实现学生信息管理系统的示例
2020/11/28 Python
CSS3图片旋转特效(360/60/-360度)
2013/10/10 HTML / CSS
巧用CSS3的calc()宽度计算做响应模式布局的方法
2018/03/22 HTML / CSS
JAVA和C++区别都有哪些
2015/03/30 面试题
小学信息技术教学反思
2014/02/10 职场文书
数据保密承诺书
2014/06/03 职场文书
企业趣味活动方案
2014/08/21 职场文书
反腐倡廉警示教育活动心得体会
2014/09/04 职场文书
2014领导班子四风问题查摆思想汇报
2014/09/13 职场文书
幼儿园个人师德总结
2015/02/06 职场文书
2015年采购工作总结
2015/04/10 职场文书
浅谈pytorch中的dropout的概率p
2021/05/27 Python