前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Go 语言并发编程系列(六)—— 通道类型篇:单向通道及其使用

Go 语言并发编程系列(六)—— 通道类型篇:单向通道及其使用

作者头像
学院君
发布2019-08-29 16:29:02
1.7K0
发布2019-08-29 16:29:02
举报
文章被收录于专栏:学院君的专栏

我们介绍了管道类型的基本语法,通常,管道都是支持双向操作的:既可以往管道发送数据,也可以从管道接收数据。但在某些场景下,可能我们需要限制只能往管道发送数据,或者只能从管道接收数据,这个时候,就需要用到单向通道。

不过,这里我们需要澄清一下,通道本身还是要支持读写的,如果某个通道只支持写入操作,那么即便数据写进去了,不能被读取也毫无意义,同理,如果某个通道只支持读取操作,不能写入数据,那么通道永远是空的,从一个空的通道读取数据会导致协程的阻塞,无法执行后续代码。

因此,Go 语言支持的单向管道,实际上是在使用层面对通道进行限制,而不是语法层面:即我们在某个协程中只能对通道进行写入操作,而在另一个协程中只能对该通道进行读取操作。从这个层面来说,单向通道的作用是约束在生产协程中只能发送数据到通道,而在消费协程中只能从通道接收数据,从而让代码遵循「最小权限原则」,避免误操作和通道使用的混乱,让代码更加稳健。

下面我们就来看看如何在 Go 协程之间实现单向通道的约束。

当我们将一个通道类型变量传递到一个函数时(通常是在另外一个协程中执行),如果这个函数只能发送数据到通道,可以通过如下将其指定为单向只写通道(发送通道):

代码语言:javascript
复制
func test(ch chan<- int)

上述代码限定在 test 函数中只能写入 int 类型数据到通道 ch

反过来,如果我们将一个通道类型变量传递到一个只允许从该通道读取数据的函数,可以通过如下方式将通道指定为单向只读通道(接收通道):

代码语言:javascript
复制
func test(ch <-chan int)

上述代码限定在 test 函数中只能从 ch 通道读取 int 类型数据。

虽然我们也可以像声明正常通道类型那样声明单向通道,但我们一般不这么做,因为这样一来,就是从语法上限定通道的操作类型了,对于只读通道只能接收数据,对于只写通道只能发送数据:

代码语言:javascript
复制
var ch1 chan int
var ch2 <-chan int 
var ch3 chan<- int

单向通道的初始化和双向通道一样:

代码语言:javascript
复制
ch1 := make(chan int)
ch2 := make(<-chan int)
ch3 := make(chan<- int)

此外,我们还可以通过如下方式实现双向通道和单向通道的转化:

代码语言:javascript
复制
ch1 := make(chan int) 
ch2 := <-chan int(ch1)
ch3 := chan<- int(ch1)

基于双向通道 ch1,我们通过类型转化初始化了两个单向通道:单向只读的 ch2 和单向只写的 ch3。注意这个转化是不可逆的,双向通道可以转化为任意类型的单向通道,但单向通道不能转化为双向通道,读写通道之间也不能相互转化。

实际上,我们在将双向通道传递到限定通道参数操作类型的函数时,就应用到了类型转化。

我们可以通过单向通道来约束上篇教程的示例代码中子协程对通道的单向写入操作:

代码语言:javascript
复制
package main

import (
    "fmt"
    "time"
)

func test(ch chan<- int) {
    for i := 0; i < 100; i++ {
        ch <- i
    }
    close(ch)
}

func main() {
    start := time.Now()
    ch := make(chan int, 20)
    go test(ch)
    for i := range ch {
        fmt.Println("接收到的数据:", i)
    }
    end := time.Now()
    consume := end.Sub(start).Seconds()
    fmt.Println("程序执行耗时(s):", consume)
}

如果我们将 test 函数中的通道参数类型约束调整为 test(ch <-chan int),编译代码就会报错:

代码语言:javascript
复制
# command-line-arguments
./channel3.go:10:12: invalid operation: ch <- i (send to receive-only type <-chan int)
./channel3.go:12:10: invalid operation: close(ch) (cannot close receive-only channel)

提示传入的通道是只读通道(receive-only channel),不能进行写入操作,此外,关闭通道函数 close 也不能作用到只读通道。

如果将 main 函数中的通道初始化语句修改为 ch := make(chan<- int),编译时也会报错:

代码语言:javascript
复制
# command-line-arguments
./channel3.go:19:14: invalid operation: range ch (receive from send-only type chan<- int)

提示不能通过 range 语句从只写通道(send-only)中接收数据。

我们也可以定义一个返回值类型为单向只写通道的函数,以便得到该返回值的代码只能从通道中接收数据:

代码语言:javascript
复制
func test() <-chan int {
    ch := make(chan int, 20)
    for i := 0; i < 100; i++ {
        ch <- i
    }
    close(ch)
    return ch
}

显然,合理使用单向通道,可以有效约束不同业务对通道的操作,避免越权使用和滥用,此外,也提高了代码的可读性,一看函数参数就可以判断出业务对通道的操作类型。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-08-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 极客书房 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档