Go缓冲channel和非缓冲channel的区别说明


Posted in Golang onApril 25, 2021

在看本篇文章前我们需要了解阻塞的概念

在执行过程中暂停,以等待某个条件的触发 ,我们就称之为阻塞

在Go中我们make一个channel有两种方式,分别是有缓冲的和没缓冲的

缓冲channel 即 buffer channel 创建方式为 make(chan TYPE,SIZE)

如 make(chan int,3) 就是创建一个int类型,缓冲大小为3的 channel

非缓冲channel 即 unbuffer channel 创建方式为 make(chan TYPE)

如 make(chan int) 就是创建一个int类型的非缓冲channel

非缓冲channel 和 缓冲channel 的区别

非缓冲 channel,channel 发送和接收动作是同时发生的

例如 ch := make(chan int) ,如果没 goroutine 读取接收者<-ch ,那么发送者ch<- 就会一直阻塞

缓冲 channel 类似一个队列,只有队列满了才可能发送阻塞

代码演示

非缓冲 channel

package main
import (
 "fmt"
 "time"
)
func loop(ch chan int) {
 for {
  select {
  case i := <-ch:
   fmt.Println("this  value of unbuffer channel", i)
  }
 }
}
func main() {
 ch := make(chan int)
 ch <- 1
 go loop(ch)
 time.Sleep(1 * time.Millisecond)
}

这里会报错 fatal error: all goroutines are asleep - deadlock! 就是因为 ch<-1 发送了,但是同时没有接收者,所以就发生了阻塞

但如果我们把 ch <- 1 放到 go loop(ch) 下面,程序就会正常运行

缓冲 channel

的阻塞只会发生在 channel 的缓冲使用完的情况下

package main
import (
 "fmt"
 "time"
)
func loop(ch chan int) {
 for {
  select {
  case i := <-ch:
   fmt.Println("this  value of unbuffer channel", i)
  }
 }
}
func main() {
 ch := make(chan int,3)
 ch <- 1
 ch <- 2
 ch <- 3
 ch <- 4
 go loop(ch)
 time.Sleep(1 * time.Millisecond)
}

这里也会报 fatal error: all goroutines are asleep - deadlock! ,这是因为 channel 的大小为 3 ,而我们要往里面塞 4 个数据,所以就会阻塞住

解决的办法有两个

把 channel 开大一点,这是最简单的方法,也是最暴力的

把 channel 的信息发送者 ch <- 1 这些代码移动到 go loop(ch) 下面 ,让 channel 实时消费就不会导致阻塞了

补充:3种优雅的Go channel用法

写Go的人应该都听过Rob Pike的这句话

Do not communicate by sharing memory; instead, share memory by communicating.

相信很多朋友和我一样,在实际应用中总感觉不到好处,为了用channel而用。但以我的切身体会来说,这是写代码时碰到的场景不复杂、对channel不熟悉导致的,所以希望这篇文章能给大家带来点新思路,对Golang优雅的channel有更深的认识 :)

Fan In/Out

数据的输出有时候需要做扇出/入(Fan In/Out),但是在函数中调用常常得修改接口,而且上下游对于数据的依赖程度非常高,所以一般使用通过channel进行Fan In/Out,这样就可以轻易实现类似于shell里的管道。

func fanIn(input1, input2 <-chan string) <-chan string {
   c := make(chan string)
   go func() {
       for {
           select {
           case s := <-input1:  c <- s
           case s := <-input2:  c <- s
           }
       }
   }()
   return c
}

同步Goroutine

两个goroutine之间同步状态,例如A goroutine需要让B goroutine退出,一般做法如下:

func main() {
   g = make(chan int)
   quit = make(chan bool)
   go B()
   for i := 0; i < 3; i++ {
       g <- i
   }
   quit <- true // 没办法等待B的退出只能Sleep
   fmt.Println("Main quit")
}
func B() {
   for {
       select {
       case i := <-g:
           fmt.Println(i + 1)
       case <-quit:
           fmt.Println("B quit")
           return
       }
   }
}
/*
Output:
1
2
3
Main quit
*/

可是了main函数没办法等待B合适地退出,所以B quit 没办法打印,程序直接退出了。

然而,chan是Go里的第一对象,所以可以把chan传入chan中,所以上面的代码可以把quit 定义为chan chan bool,以此控制两个goroutine的同步

func main() {
   g = make(chan int)
   quit = make(chan chan bool)
   go B()
   for i := 0; i < 5; i++ {
       g <- i
   }
   wait := make(chan bool)
   quit <- wait
   <-wait //这样就可以等待B的退出了
   fmt.Println("Main Quit")
}
func B() {
   for {
       select {
       case i := <-g:
           fmt.Println(i + 1)
       case c := <-quit:
           c <- true
           fmt.Println("B Quit")
           return
       }
   }
}
/* Output
1
2
3
B Quit
Main Quit
*/

分布式递归调用

在现实生活中,如果你要找美国总统聊天,你会怎么做?

第一步打电话给在美国的朋友,然后他们也会发动自己的关系网,再找可能认识美国总统的人,以此类推,直到找到为止。

这在Kadmelia分布式系统中也是一样的,如果需要获取目标ID信息,那么就不停地查询,被查询节点就算没有相关信息,也会返回它觉得最近节点,直到找到ID或者等待超时。

好了,这个要用Go来实现怎么做呢?

func recursiveCall(ctx context.Context, id []byte, initialNodes []*node){
	seen := map[string]*node{} //已见过的节点记录
	request := make(chan *node, 3) //设置请求节点channel
        // 输入初始节点
	go func() {
		for _, n := range initialNodes {
			request <- n
		}
	}()
OUT:
	for {
               //循环直到找到数据
		if data != nil {
		    return
		}
                // 在新的请求,超时和上层取消请求中select
		select {
		case n := <-request:
			go func() {
                                // 发送新的请求
				response := s.sendQuery(ctx, n, MethodFindValue, id)
				select {
				case <-ctx.Done():
				case msg :=<-response:
                                    seen[responseToNode(response)] = n //更新已见过的节点信息
                                                // 加载新的节点
						for _, rn := range LoadNodeInfoFromByte(msg[PayLoadStart:]) {
							mu.Lock()
							_, ok := seen[rn.HexID()]
							mu.Unlock()
                                                        // 见过了,跳过这个节点
							if ok { 
 								continue
							}
							AddNode(rn)
                                                        // 将新的节点送入channel
							request <- rn
						}
					}
				}
			}()
		case <-time.After(500 * time.Millisecond):
			break OUT // break至外层,否则仅仅是跳至loop外
        	case <-ctx.Done():
			break OUT
		}
	}
	return
}

这时的buffered channel类似于一个局部queue,对需要的节点进行处理,但这段代码的精妙之处在于,这里的block操作是select的,随时可以取消,而不是要等待或者对queue的长度有认识。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持三水点靠木。如有错误或未考虑完全的地方,望不吝赐教。

Golang 相关文章推荐
一文读懂go中semaphore(信号量)源码
Apr 03 Golang
go:垃圾回收GC触发条件详解
Apr 24 Golang
Go语言使用select{}阻塞main函数介绍
Apr 25 Golang
Golang 使用Map实现去重与set的功能操作
Apr 29 Golang
解决goland 导入项目后import里的包报红问题
May 06 Golang
关于golang高并发的实现与注意事项说明
May 08 Golang
go 实现简易端口扫描的示例
May 22 Golang
Go遍历struct,map,slice的实现
Jun 13 Golang
Go语言设计模式之结构型模式
Jun 22 Golang
go使用Gin框架利用阿里云实现短信验证码功能
Aug 04 Golang
Golang ort 中的sortInts 方法
Apr 24 Golang
Go中使用gjson来操作JSON数据的实现
Aug 14 Golang
Go语言使用select{}阻塞main函数介绍
win10下go mod配置方式
Go语言-为什么返回值为接口类型,却返回结构体
Apr 24 #Golang
go:垃圾回收GC触发条件详解
Apr 24 #Golang
基于go interface{}==nil 的几种坑及原理分析
Apr 24 #Golang
golang interface判断为空nil的实现代码
Apr 24 #Golang
golang判断key是否在map中的代码
Apr 24 #Golang
You might like
后宫无数却洁身自好的男主,唐三只爱小舞
2020/03/02 国漫
PHP中error_log()函数的使用方法
2015/01/20 PHP
PHP实现图片上传并压缩
2015/12/22 PHP
php时间戳转换代码详解
2019/08/04 PHP
理解JavaScript中的事件
2006/09/23 Javascript
论坛里点击别人帖子下面的回复,回复标题变成“回复 24# 的帖子”
2009/06/14 Javascript
Javascript 通过json自动生成Dom的代码
2010/04/01 Javascript
手把手教你自己写一个js表单验证框架的方法
2010/09/14 Javascript
基于jquery的内容循环滚动小模块(仿新浪微博未登录首页滚动微博显示)
2011/03/28 Javascript
JavaScript定义类的几种方式总结
2014/01/06 Javascript
extjs 如何给column 加上提示
2014/07/29 Javascript
js实现三张图(文)片一起切换的banner焦点图
2015/08/25 Javascript
jQuery插件jquery-barcode实现条码打印的方法
2015/11/25 Javascript
AngularJS 文件上传控件 ng-file-upload详解
2017/01/13 Javascript
详解操作虚拟dom模拟react视图渲染
2018/07/25 Javascript
基于Vue实现微信小程序的图文编辑器
2018/07/25 Javascript
详解用vue2.x版本+adminLTE开源框架搭建后台应用模版
2019/03/15 Javascript
一篇文章带你使用Typescript封装一个Vue组件(简单易懂)
2020/06/05 Javascript
js实现简易计算器小功能
2020/11/18 Javascript
python解析xml文件实例分析
2015/05/27 Python
分享一下Python 开发者节省时间的10个方法
2015/10/02 Python
Python简单实现enum功能的方法
2016/04/25 Python
Python自定义主从分布式架构实例分析
2016/09/19 Python
Python爬虫实现(伪)球迷速成
2018/06/10 Python
详解通过API管理或定制开发ECS实例
2018/09/30 Python
Python类装饰器实现方法详解
2018/12/21 Python
python颜色随机生成器的实例代码
2020/01/10 Python
jupyter实现重新加载模块
2020/04/16 Python
html5自带表单验证体验优化及提示气泡修改功能
2017/09/12 HTML / CSS
HTML5页面嵌入小程序没有返回按钮及返回页面空白的问题
2020/05/28 HTML / CSS
财务人员的自我评价范文
2014/03/03 职场文书
元旦晚会主持词
2014/03/24 职场文书
养成教育经验材料
2014/05/26 职场文书
护士医德医风自我评价
2014/09/15 职场文书
教你如何使用Python实现二叉树结构及三种遍历
2021/06/18 Python
python中validators库的使用方法详解
2022/09/23 Python