一.简单说明

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介绍的更多相关文章

  1. go语言之进阶篇 channel介绍

    1.channel介绍 和map类似,channel也一个对应make创建的底层数据结构的引用. 当我们复制一个channel或用于函数参数传递时,我们只是拷贝了一个channel引用,因此调用者何被 ...

  2. 一份尽可能全面的Go channel介绍

    写在前面 针对目前网络上Go channel知识点较为分散(很难有单独的一份资料把所有知识点都囊括进来)的情况,在下斗胆站在巨人的肩膀上,总结了前辈的工作,并加入了自己的理解,形成了这篇文章.本文类似 ...

  3. channel 介绍

    !!!1.Memory Channel 内存通道 事件将被存储在内存中的具有指定大小的队列中. 非常适合那些需要高吞吐量但是失败是会丢失数据的场景下.   属性说明: !type – 类型,必须是“m ...

  4. golang的Channel

    golang的Channel Channel 是 golang 一个非常重要的概念,如果你是刚开始使用 golang 的开发者,你可能还没有真正接触这一概念,本篇我们将分析 golang 的Chann ...

  5. Port Channel and VPC

    1.Port Channel 介绍 Port Channel  简介 绑定多个物理链路(最多8条),到一个单一的逻辑链路,在两个物理设备之间 每个物理端口只能被放入一个port-channel中. 在 ...

  6. java学习-NIO(三)Channel

    通道(Channel)是java.nio的第二个主要创新.它们既不是一个扩展也不是一项增强,而是全新.极好的Java I/O示例,提供与I/O服务的直接连接.Channel用于在字节缓冲区和位于通道另 ...

  7. 由浅入深剖析 go channel

    原文:https://www.jianshu.com/p/24ede9e90490 ---------------------------------- 由浅入深剖析 go channel chann ...

  8. 8.3 Go channel

    8.3 Go channel 在Go语言中,关键字go的引入使得Go语言并发编程更加简单而优雅,但是并发编程的复杂性,以及时刻关注并发编程容易出现的问题需要时刻警惕. 并发编程的难度在于协调,然而协调 ...

  9. netty核心组件之channel、handler、ChannelHandlerContext、pipeline

    channel介绍: netty中channel分为NioServerScoketChannel和NioSocketChannel,分别对应java nio中的ServerScoketChannel和 ...

  10. golang channel原理

    channel介绍 channel一个类型管道,通过它可以在goroutine之间发送和接收消息.它是Golang在语言层面提供的goroutine间的通信方式. 众所周知,Go依赖于称为CSP(Co ...

随机推荐

  1. node_modules/@umijs/runtime" does not exist in container.

    使用 umi 脚手架搭建项目,启动时报错 node_modules/@umijs/runtime" does not exist in container. 出现问题 .umi 是临时文件夹 ...

  2. Ubuntu上安装MySQL / MariaDB

    目录 在Ubuntu上安装MySQL 更新Ubuntu 安装MySQL 安全的MySQL 优化MySQL(仅限高级用户) 如何在Ubuntu上安装MariaDB 更新Ubuntu 安装MariaDB ...

  3. PIL或Pillow学习1

    PIL( Python Imaging Library)是 Python 的第三方图像处理库,由于其功能丰富,API 简洁易用,因此深受好评. 自 2011 年以来,由于 PIL 库更新缓慢,目前仅支 ...

  4. 文件转十六进制出现转义字符直接通过ASCII码逐字符展开的问题与修复

    近日工作中遇到某品牌电子签章系统生成的PDF文件若直接使用十六进制查看器打开,会出现转义字符被直接以ASCII编码转换为16进制字符串的问题,导致提取的文件无法匹配ASN.1格式,无法进一步对签章有效 ...

  5. Alice与Bob-整数分解问题脚本实现

    题目: 密码学历史中,有两位知名的杰出人物,Alice和Bob.他们的爱情经过置换和轮加密也难以混淆,即使是没有身份认证也可以知根知底.就像在数学王国中的素数一样,孤傲又热情.下面是一个大整数:985 ...

  6. linux(centos)配置ipv6网卡

    1.ipv6网卡配置文件和ipv4在同一个网卡配置文件中 vim /etc/sysconfig/network-scripts/ifcfg-eth0 设置好之后重启网卡生效 2.测试

  7. TCP协议详细介绍

    TCP报文格式: 字段介绍: 源/目的端口:用来标识主机上的程序 序号(seq):4个byte,指当前tcp报文段中第一个字节的序号(tcp报文中每个字节都有一个编号) 确认号(ack):4个byte ...

  8. 【Linux】5.4 Shell数组

    Shell数组 数组中可以存放多个值.Bash Shell 只支持一维数组(不支持多维数组),初始化时不需要定义数组大小(与 PHP 类似). 1. 数组赋值 与大部分编程语言类似,数组元素的下标由0 ...

  9. 使用克魔助手查看iOS 应用程序使用历史记录和耗能历史记录

    使用克魔助手查看iOS 应用程序使用历史记录和耗能历史记录 功能概述 克魔助手无需越狱即可访问iOS上各个应用程序的历史记录,包括: 最近几个月的app的详细启动时间记录,结束时间,app使用的硬件组 ...

  10. QT 可绑定属性 QProperty QObjectBindableProperty QObjectComputedProperty,简化信号、槽(SIGNAL、SLOT)机制的方法

    QT提供的可绑定属性是指这些属性可以绑定到其他值或表达式上(通常是 C++ lambda 表达式).如果属性是通过表达式进行绑定,该属性会跟随表达式自动更新.可绑定属性由 QProperty 类和 Q ...