Golang中的Channel(二)

发布时间 2023-07-07 15:32:03作者: 易先讯

上篇介绍了Channel是什么以及它的类型,这篇总结一下Channel的用法。

 

  1. 基本语法

(1)        声明channel

声明channel的语法格式为:

var ChannelName chan ElementType

与其他变量声明不同的是,在类型前面多了一个chan关键字。ElementType指的是这个channel能够传递的数据类型。

在golang中需要使用内置的make函数类创建channel的实例:

ch := make(chan int) //声明并初始化了一个名为ch的int型channel

 

(2)        发送接收数据

ch -< value //向channel中发送数据

【send操作注意】:

a)   c -< 3+4 //先求值再传递

b)   往一个已经被close掉的channel中发送数据会导致run-time panic

c)   注意channel是否有缓存。无缓存的channel只有在receiver准备好后send才能被执行。有缓存且缓存未满,send可以直接被执行。

value := <- ch //将channel中的数据读取到变量中

【receive操作注意】:

a)   从一个被close的channel中取数据不会被阻塞,而是立即返回,接收完已发送的数据会返回元素类型的零值。可以用一个额外的返回参数来检查channel是否关闭:

x, ok := <-ch
x, ok = <-ch
var x, ok = <-ch

如果ok是false,表示接收的x是产生的零值,这个channel被关闭了或者为空。

 

(3)        Select

在linux系统中,select函数用来监控一系列的文件句柄,一旦有文件句柄发生了I/O动作,select函数就会返回,主要被用来实现高并发的socket服务器程序。

Golang中,select关键字和linux的select函数功能相似,主要用于处理I/O异步问题。

select的语法与switch相似,由select开始一个新的选择块,每个选择条件由case语句来描述。

与switch语句相比,select有比较多的限制,其中最大的一条限制就是每个case语句必须是一个I/O操作。

        大致结构:

                select {

                case <-chan1:   //如果 chan1 成功读取到数据,则执行该 case                    

                case chan2 <- 1://如果成功向 chan2 写入数据,则执行该 case            

                default:   // 如果上面的条件都没有成功,则执行 default 流程

                }

可以看出,select 不像 switch,后面并没有条件判断,而是直接去查看 case 语句。每个 case 语句都必须是一个面向 channel 的操作。

 

  1. channel的遍历

for …… range语句可以遍历channel。但是它会一直迭代直至channel被关闭。

 

图中例子,如果把close(c)注释掉,程序会一直阻塞在for…range那行。

再看一个例子:

 

这段代码可以正常执行。

 

换成这段代码,会出现fatal error: all goroutines are asleep - deadlock!

总之,for…range是阻塞式读取channel,只有channel close后才会结束。

 

  1. 处理超时

在使用channel的时候要小心,比如

i := <= ch

当碰到永远没有往ch中写入数据的情况,那么这个读取就永远无法成功。导致的结果就是整个goroutine永远阻塞并且没有挽回的机会。

Golang没有提供直接的超时处理机制。可以利用select机制来解决,因为select的特点是只要其中一个case满足,程序就会继续往下执行,而不会考虑其他的case。基于此特性,可以实现一个channel的超时机制:

ch := make(chan int)

// 首先实现并执行一个匿名的超时等待函数

timeout := make(chan bool, 1)

go func() {

    time.Sleep(1e9) // 等待 1 

    timeout <- true

}()

// 然后把 timeout 这个 channel 利用起来

select {

    case <-ch:

    //  ch 中读取到数据

    case <- timeout:

    // 一直没有从 ch 中读取到数据,但从 timeout 中读取到了数据

    fmt.Println("Timeout occurred.")

}

执行上述代码,输出结果为:

          Timeout occurred.
  1. 关闭channel

内建的close方法可以用来关闭channel:

        close(ch)

判断一个channel是否已关闭,可以增加一个布尔类型的值:

        x, ok := <-ch

如果ok的值为false,就表示已经被关闭。