Go Channel介绍
一.简单说明
Channel是Go中的一个核心类型,可以看做是一个管道,通过它并发核心单元就可以发送或者接收数据进行通讯。
1.1 操作符
这里Channel的操作符是箭头<-,箭头的指向就是数据的流向。
ch <- v # 发送值v到Channel ch中
v := <-ch # 从Channel ch中接收数据,并将数据赋值给v
这里channel和map以及slice一样,必须先创建再使用。
ch := make(chan int)
1.2 Channel类型
Channel类型的定义格式如下:
ChannelType = ( "chan" | "chan" "<-" | "<-" "chan" ) ElementType .
它包括三种类型的定义,可选的<-代表channel的方向,如果没有指定方向,那么Channel就是双向的,既可以接收数据,也可以发送数据。
chan T # 可以接收和发送类型为T的数据
chan<- float64 # 只可以用来发送float64类型的数据
<-chan int # 只可以用来接收int类型的数据
使用make初始化Channel,可以设置容量:
make(chan int, 1000)
容量capacity代表Channel容纳的最多的元素的数量,代表Channel的缓存的大小。通过缓存的使用,可以尽量避免阻塞,提供应用的性能。
如果没有设置容量,或者容量设置为0,说明Channel没有缓存,只有sender和receiver都准备好了后它们的通讯才会发生。如果设置了缓存,就有可能不会发生阻塞,只有buffer满了后send才会阻塞,而只有缓存空了后receiver才会则色,一个nil channel不会通信。
可以通过内建的close()方法关闭Channel。
Channel是一个先入先出(FIFO)的队列,接收的数据和发送的数据顺序是一致的。
1.3 send
send语句用来往Channel中发送数据,如:ch <- 3 。它的定义如下:
SendStmt = Channel "<-" Expression .
Channel = Expression .
在通信开始前,Channel和expression必须先求值处理,如下下面的(1+2)先计算出来3然后再发送给Channel:
c := make(chan int)
defer close(c)
go func() { c <- 1 + 2 }()
i := <-c
fmt.Println(i)
send被执行前通讯一直被阻塞着,无缓存的channel只有在receiver准备好后send才被执行。如果有缓存,且缓存未满,则send会被执行。
注意:往一个已经被close的channel中继续发送数据会导致run-time panic,往nil channel中发送数据会一直被阻塞着。
1.4 receive
<-ch 用来从channel ch中接收数据,这个表达时候会被一直block,直到有数据可以接收。
注意:从一个nil channel中接收数据会一直被block。从一个被close的channel中接收数据不会被阻塞,而是立即返回,接收完已发送的数据后会返回元数类型的零值。
这里我们可以使用一个额外的返回参数来检查channel是否关闭:
x, ok := <-ch
x, ok = <-ch
var x, ok = <-ch
如果ok为false,表示接收的x是产生的零值,这个Channel被关闭了或者为空。
1.5 blocking
缺省情况下,发送和接收会一直阻塞着,直到另一方准备好。这种方式可以用来在gororutine中进行同步,而不必使用显示的锁或者条件变量。
如官方的例子中x, y := <-c, <-c这句会一直等待计算结果发送到channel中。
import "fmt"
func sum(s []int, c chan int) {
sum := 0
for _, v := range s {
sum += v
}
c <- sum // send sum to c
}
func main() {
s := []int{7, 2, 8, -9, 4, 0}
c := make(chan int)
go sum(s[:len(s)/2], c)
go sum(s[len(s)/2:], c)
x, y := <-c, <-c // receive from c
fmt.Println(x, y, x+y)
}
1.6 Range
for …… range语句可以处理Channel。
func main() {
go func() {
time.Sleep(1 * time.Hour)
}()
c := make(chan int)
go func() {
for i := 0; i < 10; i = i + 1 {
c <- i
}
close(c)
}()
for i := range c {
fmt.Println(i)
}
fmt.Println("Finished")
}
range c产生的迭代值为Channel中发送的值,它会一直迭代直到channel被关闭。上面的例子中如果把close(c)注释掉,程序会一直阻塞在for …… range那一行。
1.7 select
select语句选择一组可能的send操作和receive操作去处理。它类似switch,但是只是用来处理通讯操作。
它的case可以是send语句,也可以是receive语句,亦或者default。receive语句可以将值赋值给一个或者两个变量。它必须是一个receive操作。最多允许有一个default case,它可以放在case列表的任何位置,尽管我们大部分会将它放在最后。
import "fmt"
func fibonacci(c, quit chan int) {
x, y := 0, 1
for {
select {
case c <- x:
x, y = y, x+y
case <-quit:
fmt.Println("quit")
return
}
}
}
func main() {
c := make(chan int)
quit := make(chan int)
go func() {
for i := 0; i < 10; i++ {
fmt.Println(<-c)
}
quit <- 0
}()
fibonacci(c, quit)
}
如果有同时多个case去处理,比如同时有多个channel可以接收数据,那么Go会伪随机的选择一个case处理(pseudo-random)。如果没有case需要处理,则会选择default去处理,如果default case存在的情况下。如果没有default case,则select语句会阻塞,直到某个case需要处理。
注意:需要注意的是,nil channel上的操作会一直被阻塞,如果没有default case,只有nil channel的select会一直被阻塞。
elect语句和switch语句一样,它不是循环,它只会选择一个case来处理,如果想一直处理channel,你可以在外面加一个无限的for循环:
or {
select {
case c <- x:
x, y = y, x+y
case <-quit:
fmt.Println("quit")
return
}
}
1.8 timeout
select有很重要的一个应用就是超时处理。 因为上面我们提到,如果没有case需要处理,select语句就会一直阻塞着。这时候我们可能就需要一个超时操作,用来处理超时的情况。
下面这个例子我们会在2秒后往channel c1中发送一个数据,但是select设置为1秒超时,因此我们会打印出timeout 1,而不是result 1。
import "time"
import "fmt"
func main() {
c1 := make(chan string, 1)
go func() {
time.Sleep(time.Second * 2)
c1 <- "result 1"
}()
select {
case res := <-c1:
fmt.Println(res)
case <-time.After(time.Second * 1):
fmt.Println("timeout 1")
}
}
其实它利用的是time.After方法,它返回一个类型为<-chan Time的单向的channel,在指定的时间发送一个当前时间给返回的channel中。
1.9 Timer和Ticker
timer是一个定时器,代表未来的一个单一事件,你可以告诉timer你要等待多长时间,它提供一个Channel,在将来的那个时间那个Channel提供了一个时间值。下面的例子中第二行会阻塞2秒钟左右的时间,直到时间到了才会继续执行。
timer1 := time.NewTimer(time.Second * 2)
<-timer1.C
fmt.Println("Timer 1 expired")
当然如果你只是想单纯的等待的话,可以使用time.Sleep来实现。
还可以使用timer.Stop来停止计时器。
timer2 := time.NewTimer(time.Second)
go func() {
<-timer2.C
fmt.Println("Timer 2 expired")
}()
stop2 := timer2.Stop()
if stop2 {
fmt.Println("Timer 2 stopped")
}
ticker是一个定时触发的计时器,它会以一个间隔(interval)往Channel发送一个事件(当前时间),而Channel的接收者可以以固定的时间间隔从Channel中读取事件。下面的例子中ticker每500毫秒触发一次,你可以观察输出的时间。
ticker := time.NewTicker(time.Millisecond * 500)
go func() {
for t := range ticker.C {
fmt.Println("Tick at", t)
}
}()
类似timer, ticker也可以通过Stop方法来停止。一旦它停止,接收者不再会从channel中接收数据了。
1.10 同步
channel可以用在goroutine之间的同步。
下面的例子中main goroutine通过done channel等待worker完成任务。 worker做完任务后只需往channel发送一个数据就可以通知main goroutine任务完成。
import (
"fmt"
"time"
)
func worker(done chan bool) {
time.Sleep(time.Second)
// 通知任务已完成
done <- true
}
func main() {
done := make(chan bool, 1)
go worker(done)
// 等待任务完成
<-done
}
1.11 示例说明
package main
import (
"fmt"
"time"
)
// channel一般用于并发较大的场景,也可以用于做计划任务,消息队列之类的功能
func send(ch_test chan int) {
ch_test <- 1
ch_test <- 2
ch_test <- 3
ch_test <- 4
}
func receive(ch_test chan int) {
var recv int
for { // 无限循环 由于receive要不断从channel中读取可能存在的数据,所以receive一般使用一个无限循环来读取channel,避免send发送的数据被丢弃
recv = <-ch_test
fmt.Println(recv)
}
}
func main() {
ch_test := make(chan int)
// 两个goroutine,一个用于执行send函数,该函数每次向channel ch_test中发送写入一个int数值,receive函数每次从chan_test中读取一个int数值
go send(ch_test)
go receive(ch_test)
time.Sleep(time.Duration(2) * time.Second) // 当main函数睡眠时间到了指定的时间main程序终止,也就是主程序结束,所有goroutine停止运行
}
参考链接:
https://gobyexample.com/channels
https://colobu.com/2016/04/14/Golang-Channels/
Go Channel介绍的更多相关文章
- go语言之进阶篇 channel介绍
1.channel介绍 和map类似,channel也一个对应make创建的底层数据结构的引用. 当我们复制一个channel或用于函数参数传递时,我们只是拷贝了一个channel引用,因此调用者何被 ...
- 一份尽可能全面的Go channel介绍
写在前面 针对目前网络上Go channel知识点较为分散(很难有单独的一份资料把所有知识点都囊括进来)的情况,在下斗胆站在巨人的肩膀上,总结了前辈的工作,并加入了自己的理解,形成了这篇文章.本文类似 ...
- channel 介绍
!!!1.Memory Channel 内存通道 事件将被存储在内存中的具有指定大小的队列中. 非常适合那些需要高吞吐量但是失败是会丢失数据的场景下. 属性说明: !type – 类型,必须是“m ...
- golang的Channel
golang的Channel Channel 是 golang 一个非常重要的概念,如果你是刚开始使用 golang 的开发者,你可能还没有真正接触这一概念,本篇我们将分析 golang 的Chann ...
- Port Channel and VPC
1.Port Channel 介绍 Port Channel 简介 绑定多个物理链路(最多8条),到一个单一的逻辑链路,在两个物理设备之间 每个物理端口只能被放入一个port-channel中. 在 ...
- java学习-NIO(三)Channel
通道(Channel)是java.nio的第二个主要创新.它们既不是一个扩展也不是一项增强,而是全新.极好的Java I/O示例,提供与I/O服务的直接连接.Channel用于在字节缓冲区和位于通道另 ...
- 由浅入深剖析 go channel
原文:https://www.jianshu.com/p/24ede9e90490 ---------------------------------- 由浅入深剖析 go channel chann ...
- 8.3 Go channel
8.3 Go channel 在Go语言中,关键字go的引入使得Go语言并发编程更加简单而优雅,但是并发编程的复杂性,以及时刻关注并发编程容易出现的问题需要时刻警惕. 并发编程的难度在于协调,然而协调 ...
- netty核心组件之channel、handler、ChannelHandlerContext、pipeline
channel介绍: netty中channel分为NioServerScoketChannel和NioSocketChannel,分别对应java nio中的ServerScoketChannel和 ...
- golang channel原理
channel介绍 channel一个类型管道,通过它可以在goroutine之间发送和接收消息.它是Golang在语言层面提供的goroutine间的通信方式. 众所周知,Go依赖于称为CSP(Co ...
随机推荐
- node_modules/@umijs/runtime" does not exist in container.
使用 umi 脚手架搭建项目,启动时报错 node_modules/@umijs/runtime" does not exist in container. 出现问题 .umi 是临时文件夹 ...
- Ubuntu上安装MySQL / MariaDB
目录 在Ubuntu上安装MySQL 更新Ubuntu 安装MySQL 安全的MySQL 优化MySQL(仅限高级用户) 如何在Ubuntu上安装MariaDB 更新Ubuntu 安装MariaDB ...
- PIL或Pillow学习1
PIL( Python Imaging Library)是 Python 的第三方图像处理库,由于其功能丰富,API 简洁易用,因此深受好评. 自 2011 年以来,由于 PIL 库更新缓慢,目前仅支 ...
- 文件转十六进制出现转义字符直接通过ASCII码逐字符展开的问题与修复
近日工作中遇到某品牌电子签章系统生成的PDF文件若直接使用十六进制查看器打开,会出现转义字符被直接以ASCII编码转换为16进制字符串的问题,导致提取的文件无法匹配ASN.1格式,无法进一步对签章有效 ...
- Alice与Bob-整数分解问题脚本实现
题目: 密码学历史中,有两位知名的杰出人物,Alice和Bob.他们的爱情经过置换和轮加密也难以混淆,即使是没有身份认证也可以知根知底.就像在数学王国中的素数一样,孤傲又热情.下面是一个大整数:985 ...
- linux(centos)配置ipv6网卡
1.ipv6网卡配置文件和ipv4在同一个网卡配置文件中 vim /etc/sysconfig/network-scripts/ifcfg-eth0 设置好之后重启网卡生效 2.测试
- TCP协议详细介绍
TCP报文格式: 字段介绍: 源/目的端口:用来标识主机上的程序 序号(seq):4个byte,指当前tcp报文段中第一个字节的序号(tcp报文中每个字节都有一个编号) 确认号(ack):4个byte ...
- 【Linux】5.4 Shell数组
Shell数组 数组中可以存放多个值.Bash Shell 只支持一维数组(不支持多维数组),初始化时不需要定义数组大小(与 PHP 类似). 1. 数组赋值 与大部分编程语言类似,数组元素的下标由0 ...
- 使用克魔助手查看iOS 应用程序使用历史记录和耗能历史记录
使用克魔助手查看iOS 应用程序使用历史记录和耗能历史记录 功能概述 克魔助手无需越狱即可访问iOS上各个应用程序的历史记录,包括: 最近几个月的app的详细启动时间记录,结束时间,app使用的硬件组 ...
- QT 可绑定属性 QProperty QObjectBindableProperty QObjectComputedProperty,简化信号、槽(SIGNAL、SLOT)机制的方法
QT提供的可绑定属性是指这些属性可以绑定到其他值或表达式上(通常是 C++ lambda 表达式).如果属性是通过表达式进行绑定,该属性会跟随表达式自动更新.可绑定属性由 QProperty 类和 Q ...