channel

channel是goroutine之间的通信机制,它可以让一个goroutine通过它给另一个goroutine发送数据,每个channel在创建的时候必须指定一个类型,指定的类型是任意的。

使用内置的make函数,可以创建一个channel类型:

ch := make(chan int)

发送和接受

channel主要的操作有发送和接受:

// 发送数据到channel
ch <- 1
// 从channel接受数据
x := <- ch

如向channel发送数据的时候,该goroutine会一直阻塞直到另一个goroutine接受该channel的数据,反之亦然,goroutine接受channel的数据的时候也会一直阻塞直到另一个goroutine向该channel发送数据,如下面操作:

func main() {
ch := make(chan string)
// 在此处阻塞,然后程序会弹出死锁的报错
c <- "hello"
fmt.Println("channel has send data")
}

正确的操作:

func main() {
ch := make(chan string)
go func(){
// 在执行到这一步的时候main goroutine才会停止阻塞
str := <- ch
fmt.Println("receive data:" + str)
}()
ch <- "hello"
fmt.Println("channel has send data")
}

说到channel的阻塞,就不得不说到有缓冲的channel。

带缓冲的channel

带缓冲的channel的创建和不带缓冲的channel(也就是上面用的channel)的创建差不多,只是在make函数的第二个参数指定缓冲的大小。

// 创建一个容量为10的channel
ch := make(chan int, 10)

带缓冲的channel就像一个队列,遵从先进先从的原则,发送数据向队列尾部添加数据,从头部接受数据。

goroutine向channel发送数据的时候如果缓冲还没满,那么该goroutine就不会阻塞。

ch := make(chan int, 2)
// 前面两次发送数据不会阻塞,因为缓冲还没满
ch <- 1
ch <- 2
// goroutine会在这里阻塞
ch <- 3

反之如果接受该channel数据的时候,如果缓冲有数据,那么该goroutine就不会阻塞。

channel与goroutine之间的应用可以想象成某个工厂的流水线工作,流水线上面有打磨,上色两个步骤(两个goroutine),负责打磨的工人生产完成后会传给负责上色的工人,上色的生产依赖于打磨,两个步骤之间的可能存在存放槽(channel),如果存放槽存满了,打磨工人就不能继续向存放槽当中存放产品,直到上色工人拿走产品,反之上色工人如果把存放槽中的产品都上色完毕,那么他就只能等待新的产品投放到存放槽中。

备注

其实在实际应用中,带缓冲的channel用的并不多,继续拿刚才的流水线来做案例,如果打磨工人生产速度比上色工人工作速度要快,那么即便再多容量的channel,也会迟早被填满然后打磨工人会被阻塞,反之如果上色工人生产速度大于打磨工人速度,那么有缓冲的channel也是一直处于没有数据,上色工人很容易长时间处于阻塞的状态。

因此比较好的解决方法还是针对生产速度较慢的一方多加人手,也就是多开几个goroutine来进行处理,有缓冲的channel最好用处只是拿来防止goroutine的完成时间有一定的波动,需要把结果缓冲起来,以平衡整体channel通信。

单方向的channel

使用channel来使不同的goroutine去进行通信,很多时候都和消费者生产者模式很相似,一个goroutine生产的结果都用channel传送给另一个goroutine,一个goroutine的执行依赖与另一个goroutine的结果。

因此很多情况下,channel都是单方向的,在go里面可以把一个无方向的channel转换为只接受或者只发送的channel,但是却不能反过来把接受或发送的channel转换为无方向的channel,适当地把channel改成单方向,可以达到程序强约束的做法,类似于下面例子:

fuc main(){
ch := make(ch chan string) go func(out chan<- string){
out <- "hello"
}(ch) go func(in <-chan string){
fmt.Println(in)
}(ch) time.Sleep(2 * time.Second)
}

select多路复用

在一个goroutine里面,对channel的操作很可能导致我们当前的goroutine阻塞,而我们之后的操作都进行不了。而如果我们又需要在当前channel阻塞进行其他操作,如操作其他channel或直接跳过阻塞,可以通过select来达到多个channel(可同时接受和发送)复用。如下面我们的程序需要同时监听多个频道的信息:

broadcaster1 := make(chan string) // 频道1
broadcaster2 := make(chan string) // 频道2
select {
case mess1 := <-broadcaster1:
fmt.Println("来自频道1的消息:" + mess1)
case mess2 := <-broadcaster2:
fmt.Println("来自频道2的消息:" + mess2)
default:
fmt.Println("暂时没有任何频道的消息,请稍后再来~")
time.Sleep(2 * time.Second)
}

select和switch语句有点相似,找到匹配的case执行对应的语句块,但是如果有两个或以上匹配的case语句,那么则会随机选择一个执行,如果都不匹配就会执行default语句块(如果含有default的部分的话)。

值得注意的是,select一般配合for循环来达到不断轮询管道的效果,可能很多小伙伴想着写个在某个case里用break来跳出for循环,这是不行的,因为break只会退出当前case,需要使用return来跳出函数或者弄个标志位标记退出

var flag = 0
for {
if flag == 1 {break}
select {
case message := <- user.RecMess :
event := gjson.Get(string(message), "event").String()
if event == "login" {
Login(message, user)
}
break
case <- user.End :
flag = 1
break
}
}

关闭

channel可以接受和发送数据,也可以被关闭。

close(ch)

关闭channel后,所有向channel发送数据的操作都会引起panic,而被close之后的channel仍然可以接受之前已经发送成功的channel数据,如果数据全部接受完毕,那么再从channel里面接受数据只会接收到零值得数据。

channel的关闭可以用来操作其他goroutine退出,在运行机制方面,goroutine只有在自身所在函数运行完毕,或者主函数运行完毕才会打断,所以我们可以利用channel的关闭作为程序运行入口的一个标志位,如果channel关闭则停止运行。

无法直接让一个goroutine直接停止另一个goroutine,但可以使用通信的方法让一个goroutine停止另一个goroutine,如下例子就是程序一边运行,一边监听用户的输入,如果用户回车,则退出程序。

func main() {
shutdown := make(chan struct{})
var n sync.WaitGroup
n.Add(1)
go Running(shutdown, &n)
n.Add(1)
go ListenStop(shutdown, &n)
n.Wait()
} func Running(shutdown <-chan struct{}, n *sync.WaitGroup) {
defer n.Done()
for {
select {
case <-shutdown:
// 一旦关闭channel,则可以接收到nil。
fmt.Println("shutdown goroutine")
return
default:
fmt.Println("I am running")
time.Sleep(1 * time.Second)
}
}
} func ListenStop(shutdown chan<- struct{}, n *sync.WaitGroup) {
defer n.Done()
os.Stdin.Read(make([]byte, 1))
// 如果用户输入了回车则退出关闭channel
close(shutdown)
}

利用channel关闭时候的传送的零值信号可以有效地退出其他goroutine,特别是关闭多个goroutine的时候,就不需要向channel传输多个信息了。

Golang学习笔记:channel的更多相关文章

  1. golang学习笔记20 一道考察对并发多协程操作一个共享变量的面试题

    golang学习笔记20 一道考察对并发多协程操作一个共享变量的面试题 下面这个程序运行的能num结果是什么? package main import ( "fmt" " ...

  2. golang学习笔记19 用Golang实现以太坊代币转账

    golang学习笔记19 用Golang实现以太坊代币转账 在以太坊区块链中,我们称代币为Token,是以太坊区块链中每个人都可以任意发行的数字资产.并且它必须是遵循erc20标准的,至于erc20标 ...

  3. golang学习笔记18 用go语言编写移动端sdk和app开发gomobile

    golang学习笔记18 用go语言编写移动端sdk和app开发gomobile gomobile的使用-用go语言编写移动端sdk和app开发https://blog.csdn.net/u01249 ...

  4. golang学习笔记17 爬虫技术路线图,python,java,nodejs,go语言,scrapy主流框架介绍

    golang学习笔记17 爬虫技术路线图,python,java,nodejs,go语言,scrapy主流框架介绍 go语言爬虫框架:gocolly/colly,goquery,colly,chrom ...

  5. golang学习笔记16 beego orm 数据库操作

    golang学习笔记16 beego orm 数据库操作 beego ORM 是一个强大的 Go 语言 ORM 框架.她的灵感主要来自 Django ORM 和 SQLAlchemy. 目前该框架仍处 ...

  6. golang学习笔记14 golang substring 截取字符串

    golang学习笔记14 golang substring 截取字符串golang 没有java那样的substring函数,但支持直接根据 index 截取字符串mystr := "hel ...

  7. golang学习笔记13 Golang 类型转换整理 go语言string、int、int64、float64、complex 互相转换

    golang学习笔记13 Golang 类型转换整理 go语言string.int.int64.float64.complex 互相转换 #string到intint,err:=strconv.Ato ...

  8. golang学习笔记12 beego table name `xxx` repeat register, must be unique 错误问题

    golang学习笔记12 beego table name `xxx` repeat register, must be unique 错误问题 今天测试了重新建一个项目生成新的表,然后复制到旧的项目 ...

  9. golang学习笔记11 golang要用jetbrain的golang这个IDE工具开发才好

    golang学习笔记11   golang要用jetbrain的golang这个IDE工具开发才好  jetbrain家的全套ide都很好用,一定要dark背景风格才装B   从File-->s ...

  10. golang学习笔记10 beego api 用jwt验证auth2 token 获取解码信息

    golang学习笔记10 beego api 用jwt验证auth2 token 获取解码信息 Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放 ...

随机推荐

  1. eclipse怎么停止building workspace

    Eclipse 一直不停 building workspace完美解决总结 一.产生这个问题的原因多种 1.自动升级 2.未正确关闭 3.maven下载lib挂起 等.. 二.解决总结 (1).解决方 ...

  2. Docker学习笔记 - Docker容器的网络基础

    一.虚拟网桥 docker0 docker0 是 linux的虚拟网桥,守护进程通过docker0给容器提供网络连接的各种服务. 网桥是数据链路层设备,通常ip地址是网络层的设置.linux的虚拟网桥 ...

  3. IDE-Android Studio 导入Ecplise项目不改变结构

    Android Studio 导入 Ecplise创建的android 项目  无导入 不修改目录结构 首先,Ecplise 原有目录结构创建的android项目一枚 Sept 1 .  打开项目 S ...

  4. 2018年Web前端自学路线

    本文最初发表于博客园,并在GitHub上持续更新前端的系列文章.欢迎在GitHub上关注我,一起入门和进阶前端. 以下是正文. Web前端入门的自学路线 新手入门前端,需要学习的基础内容有很多,如下. ...

  5. Apache Flink 分布式执行

    Flink 的分布式执行过程包含两个重要的角色,master 和 worker,参与 Flink 程序执行的有多个进程,包括 Job Manager,Task Manager 以及 Job Clien ...

  6. Ubuntu下安装最新sublime

    1. Install the GPG key: wget -qO - https://download.sublimetext.com/sublimehq-pub.gpg | sudo apt-key ...

  7. mysql自带的example测试数据库导入Centos6.5

    1.下载数据库 下载地址: [test数据库] (https://github.com/datacharmer/test_db) 不出意外的话,下载下来是个unzip文件. 2.上传到数据库服务器 r ...

  8. 字符串分割方法split()函数

    >>> data = '1000,小甲鱼,男'>>> data.split(',')['1000', '小甲鱼', '男'] str.split('以什么为标志进行 ...

  9. devops基本流程

    概要 源代码管理: 工具:Subversion (SVN) Concurrent Version System (CVS) Git SCCS Revision control systems Bitb ...

  10. C++ STL基本容器使用

    1:关联容器和顺序容器 c++中有两种类型的容器:顺序容器和关联容器,顺序容器主要有:vector.list.deque等.其中vector表示一段连续的内存地址,基于数组的实现,list表示非连续的 ...