本篇文章接着 hello world 的并发实现一文介绍 Go 的 channel 类型,同时进一步介绍 channel 的几种死锁情况,这些都是代码中很容易遇到的,要重点摘出来讲,防止一不留神程序就“死”了。

1. 为什么需要 channel?

channel 是一种通道类型,它通过发送和接收需要共享的资源,实现共享资源在 goroutine 之间的同步。说起来有点枯燥,来看一段代码:

func speak(words *string) {
*words = "hello world"
} func listen(words string) {
fmt.Printf("listen %s\n", words)
} func main() {
runtime.GOMAXPROCS(2) var words string
go speak(&words)
go listen(words)
}

这段代码的初衷是开启两个 goroutine,其中 speak goroutine 写字符 "hello world" 给 words,而 listen goroutine 打印字符。执行代码发现什么都没打印,这是因为主main goroutine 还没等两个 goroutine 运行就提前结束了。那么加上 sync 等待每个 goroutine 运行,继续改写代码:

func speak(words *string) {
defer wg.Done()
*words = "hello world"
} func listen(words string) {
defer wg.Done()
fmt.Printf("listen %s\n", words)
} func main() {
runtime.GOMAXPROCS(2) wg.Add(2)
var words string
go speak(&words)
go listen(words)
wg.Wait()
}

执行结果:

listen

可以看到两个 goroutine 都执行了,不过由于是并发执行,并没有实现我们想要的打印 hello world 效果。

因此一个问题就冒出来了,各个 goroutine 间怎么知道彼此是否执行完毕了呢?或者换个问法,各个 goroutine 间怎么实现相互同步呢?

这时候令人欢呼雀跃,让人疯狂的 channel 迈着矫健的步伐闪亮登场了,它彷佛在说快用我,快用我。没错,使用 channel 可以很好的解决这个问题。改下代码如下:

func speak(words chan string) {
defer wg.Done()
words <- "hello world"
} func listen(words chan string) {
defer wg.Done()
value := <-words
fmt.Printf("listen %s", value)
} func main() {
runtime.GOMAXPROCS(2) wg.Add(2)
words := make(chan string)
go speak(words)
go listen(words)
wg.Wait()
}

执行结果:

listen hello world

可以看到,引入 channel 很容易的打印出想要的效果。

值得注意的是: 引入 channel,这里的 sync 还是必须的,如果不要的话 main goroutine 还是会不等两个 goroutine 运行完而提前走人。

关于 channel 的使用,分类,关闭和遍历等内容不在本篇文章的讨论范围之内。接下来直接看使用 channel 会遇到的几种死锁情况。

2. channel 死锁

2.1 死锁名场面1

代码示例:

func speak(words chan string) {jing
defer wg.Done()
words <- "hello world"
} func main() {
runtime.GOMAXPROCS(2) wg.Add(2)
words := make(chan string)
go speak(words)
wg.Wait()
}

上述代码往 channel 中写入字符串,但是没有 goroutine 接收 channel 中的字符串导致程序阻塞在传值,引发死锁。

func listen(words chan string) {
defer wg.Done()
value := <-words
fmt.Printf("listen %s", value)
} func main() {
runtime.GOMAXPROCS(2) wg.Add(2)
words := make(chan string)
go listen(words)
wg.Wait()
}

与前面死锁情况类似,这里没有 goroutine 往 channel 内写字符,导致程序阻塞在取值,引发死锁。

2.2 死锁名场面2

func speak(words chan string) {
defer wg.Done()
words <- "hello world"
} func listen(words chan string) {
defer wg.Done() disturber := make(chan string)
disturber <- "hi" value := <-words
fmt.Printf("listen %s", value)
} func main() {
runtime.GOMAXPROCS(2) wg.Add(2)
words := make(chan string)
go speak(words)
go listen(words)
wg.Wait()
}

进一步的,这里添加了另一个 channel disturber,它将接收字符串。

程序依然会出现死锁。因为两个 goroutine 都在等着通道 words 和 disturber 的值被接收,即便 value 会取 words 的值,但由于 disturber 的值没有接收,程序会一直阻塞在 disturber 传值这里,导致死锁。

2.3 死锁名场面3

改写代码如下:

func cal_hello_num_(word string, ch chan int) {
defer wg.Done() /* 通道是收方双方的,如果收和方是一对多的关系则需对方发进行限制,防止竞争抢占 */
mutex.Lock()
number++
ch <- number
fmt.Printf("say: %s, %d\n", word, number)
mutex.Unlock()
} func show_hello_num_(ch chan int) {
defer wg.Done()
for {
num, ok := <-ch
if !ok {
fmt.Printf("\nnum: %d", num)
break
}
}
} func num_say_hello_channel(ch chan int) {
go cal_hello_num_("w", ch)
go cal_hello_num_("o", ch)
go cal_hello_num_("r", ch)
go cal_hello_num_("l", ch)
go cal_hello_num_("d", ch)
go show_hello_num_(ch)
} func main() {
runtime.GOMAXPROCS(2)
wg.Add(6)
ch := make(chan int)
num_say_hello_channel(ch)
wg.Wait()
}

代码很简单这里就不介绍了,查看执行结果:

say: w, 1
say: r, 2
say: o, 3
say: l, 4
say: d, 5
fatal error: all goroutines are asleep - deadlock!

报死锁了,为什么呢?

细看之下发现问题出在 for 循环这里,for 循环持续从通道 ch 读数据,当通道中无数据可读的时候相当于阻塞在通道取值,从而引发死锁。这是对于无缓冲通道的取值而言,对于有缓冲的通道也是适用的,有缓冲的通道数据读完了也相当于无缓冲的通道。

知道了哪里错了在解决起来就不难了,在最后一个 goroutine 传通道值后将通道 close 掉,后面使用 for 读取已经关闭的通道将输出 ok 为 false:

func cal_hello_num_(word string, ch chan int) {
defer wg.Done() /* 经典案例: 通道是收方双方的,如果收和方是一对多的关系则需对方发进行限制,防止竞争抢占 */
mutex.Lock()
number++
ch <- number
fmt.Printf("say: %s, %d\n", word, number) if number == 5 {
fmt.Printf("close channel")
close(ch)
} mutex.Unlock()
}

执行结果:

say: w, 1
say: o, 2
say: r, 3
say: l, 4
say: d, 5
close channel
num: 0

channel 是怎么走上死锁这条路的的更多相关文章

  1. 我是怎么走上python这条路的

    看看时间,此刻是零点43分,写了几十行代码,看了3个小时关于Django的视频,连续两个多月的坚持,突然想停下来,想想,感觉挺搞笑的... 为什么学python?我终于正式的问了自己这个问题,我想拿个 ...

  2. 数据库最佳实践:DBA小马如何走上升值加薪之路?

    DBA可能是互联网公司里面熬夜最多,背锅最多的岗位之一,腾讯云数据库团队的同学结合自身的成长经历,用漫画的形式为我们分享了一位DBA是如何从菜鸟成长为大神,走上升职加薪,迎娶白富美之路的. 此文已由作 ...

  3. IT这条路,适合什么人走。

    今天 ,到图书馆Study,呼,不知道为撒,看到那么多新书,那么多新技术(也不能说是新技术,就是自己没有学习过的技术),特别兴奋,学习的疲劳顿时间就没了,感觉什么都想学,都想据为己有,但是...... ...

  4. [项目实施失败讨论Case] “凭心而论,在这家公司很敬业的工作了3年多,老板最后给我下的评语,大家都看看吧,千万别和我走同一条路!”(摘自csdn)

    [Case] “凭心而论,在这家公司很敬业的工作了3年多,老板最后给我下的评语,大家都看看吧,千万别和我走同一条路!”(摘自csdn) 原文:http://community.csdn.net/Exp ...

  5. 发发牢骚,觉得走c#这条路,不该太浮躁。

    发发牢骚,觉得走c#这条路,不该太浮躁.校园招聘结束了,腾讯,华为,百度,完美时空,网易,阿里,让我觉得.NET很受歧视.清一色的C/C++,JAVA,只有网易有一点.Net的,但是都是非核心的运维工 ...

  6. 为什么我会选择走 Java 这条路?

    阅读本文大概需要 2.8 分钟.   作者:黄小斜 文章来源:微信公众号[程序员江湖] 最近有一些小伙伴问我,为什么当初选择走Java这条路,为什么不做C++.前端之类的方向呢,另外还有一些声音:研究 ...

  7. 神户制钢坑了500家企业 百年老店为何走上邪路?(企业经营再艰难,也不能降低产品质量,甚至偷工减料,同样适用于IT行业)

    神户制钢这颗烂萝卜,拔出它之后带出的泥越来越多.上周五社长川崎博也又开了记者会,再次道歉,而受到其数据造假影响的客户数量也从200家飙升到500家. 日本政府给神户制钢两周时间调查,还要在一个月内公布 ...

  8. ​为什么我会选择走 Java 这条路?

    ​本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tutorial 喜欢的话麻烦点 ...

  9. 是什么让我走上Java之路?

    选择方向,很多人都为根据自己的兴趣爱好和自己的能力所长而作出选择.那么是什么让我走上Java之路? 整个高三我有两门课程没有听过课,一门是数学,一门是物理.当时候物理没有听课的原因很简单,我有一本&l ...

  10. jQuery文件上传插件jQuery Upload File 有上传进度条

    jQuery文件上传插件jQuery Upload File 有上传进度条 jQuery文件上传插件jQuery Upload File,插件使用简单,支持单文件和多文件上传,支持文件拖拽上传,有进度 ...

随机推荐

  1. 基于winform(C#)的飞鸟小游戏

    本项目是一款基于C# (winform)版本的飞鸟小游戏,是一款益智类游戏 其效果如下图所示 如上图所示为飞鸟游戏的初始化界面: 可以看到游戏包含了四个功能: 启动 注册 登陆 排行榜 启动:是用于开 ...

  2. Linux下安装不同python版本的虚拟环境

    使用的是virtualenv工具安装的虚拟环境. virtualenv是一个用来建立虚拟的python环境,通常情况下,可能会碰到各种python环境,但是只有一台电脑,virtualenv就派上用场 ...

  3. 基于一维卷积神经网络模型的AI量化智能选股策略

    这是早前BigQuant专题研究:基于卷积神经网络CNN的深度学习因子选股模型.卷积神经网络(Convolutional Neural Network, CNN),是计算机视觉研究和应用领域中最具影响 ...

  4. keycloak~从login-status-iframe页面总结如何跨域传值~续

    keycloak~从login-status-iframe相关文章,可阅读我的这两篇keycloak~从login-status-iframe页面总结如何跨域传值,keycloak~对接login-s ...

  5. 技本功|Hive优化之监控(三)

    Hive是大数据领域常用的组件之一,主要是大数据离线数仓的运算,关于Hive的性能调优在日常工作和面试中是经常涉及的一个点,因此掌握一些Hive调优是必不可少的技能.影响Hive效率的主要有数据倾斜. ...

  6. Unix IPC

    本文主要是摘抄 APUE 中 IPC 部分的内容 IPC(Inter Process Communication)进程间通信,是指在进程之间进行通信的一种方式,本文将简要介绍一下在 Unix 中存在的 ...

  7. Mybatis源码1JDBC->mybatis主要流程->mybatis Excutor简介

    === 一丶mybatis概述 MyBatis 是一款优秀的持久层框架,它支持自定义 SQL.存储过程以及高级映射.MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作.My ...

  8. Luogu1419 区间问题 二分 单调优化

    原题链接 题意 给定一段长度为1e5的序列A,并且给我们一个范围 \([S, T]\), 要求我们求出一段长度在这个范围内的连续子序列,并且要使这个连续子序列的平均值最大,输出这个平均值. 思路 一开 ...

  9. 26、Flutter中命名路由

    Flutter 中的命名路由 main.dart中配置路由 void main() { runApp(MaterialApp( theme: ThemeData( appBarTheme: const ...

  10. 带你了解VXLAN网络中报文的转发机制

    摘要:本节以集中式VXLAN网络(手工方式建立VXLAN隧道)为例,分别介绍相同子网内.不同子网间是如何进行通信的.在了解转发机制的前提下,我们先来看下VXLAN网关有哪些种类. VXLAN二层网关与 ...