62

Go基础系列:双层channel用法示例

 6 years ago
source link: https://studygolang.com/articles/16445?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Go channel系列:

双层通道的解释见 Go的双层通道

以下是一个双层通道的使用示例。注意下面的示例中使用了"信号通道"(Signal channel),但这里的信号通道是多余的,仅仅只是为了介绍。

信号通道不用来传递数据,而是用来传递消息,用来产生可读、可写的事件,以便让select选中某个分支。产生消息事件的方式有多种,比如直接关闭通道、发送false/true布尔值等等。

package main

import (
    "fmt"
    "time"
)

func main() {
    // 定义双层通道cc
    cc := make(chan chan int)
    times := 5
    for i := 1; i < times+1; i++ {
        // 定义信号通道f
        f := make(chan bool)

        // 每次循环都在双层通道cc中生成内层通道c
        // 并通过信号通道f来终止f1()
        go f1(cc, f)

        // 从双层通道cc中取出内层通道ch
        // 并向ch通道发送数据
        ch := <-cc
        ch <- i

        // 从ch中取出数据
        for sum := range ch {
            fmt.Printf("Sum(%d)=%d\n", i, sum)
        }
        // 每个循环睡眠一秒钟
        time.Sleep(time.Second)
        // 每次循环都关闭信号通道f
        close(f)
    }
}

// 双层通道cc用来生成内层通道c
// 并使用信号通道f来终止函数f1()
func f1(cc chan chan int, f chan bool) {
    c := make(chan int)
    cc <- c
    defer close(c)
    sum := 0
    select {
    // 从内层通道中取出数据,计算和,然后发回内层通道
    case x := <-c:
        for i := 0; i <= x; i++ {
            sum = sum + i
        }
        // goroutine将阻塞在此,直到数据被读走
        c <- sum
    // 信号通道f可读时,结束f1()的运行
    // 但因为select没有在for中,该case分支用不上
    case <-f:
        return
    }
}

上面的示例中,函数f1()两个参数,一个是双层通道cc,一个是信号通道f。f1()中首先生成了一个通道c,并发送给了双层通道cc,使得main()中可以从cc中取得这个内层通道c,并向其发送数据。

回到f1()中,select最初会被阻塞,因为内层通道c和信号通道f都没有数据可读。由于main()可以取得内层通道c,并向其发送数据,使得f1()中的select第一个case分支被选中,该分支会计算发送的整数之前的总和,并将计算结果重新发送给内层通道c,让main()可以取得这个计算结果。

上面的示例中有几个细节需要注意:

  1. 在f1()中必须关闭内层通道c,因为main()中的range迭代一个未关闭的通道会一直阻塞,而且每次调用f1()都会重新创建c通道。
  2. 上面的信号通道其实没有起到任何作用。
  3. f1()中的select必须不能放进for循环。因为f1()将数据发回c之后,如果在for中,发f()所在的goroutine将阻塞在select上,由于c通道还没有关闭,这会导致main goroutine因range迭代操作而阻塞,也就是说所有goroutine都被阻塞了,出现了死锁。

所以,当在select中有发送操作的时候,很可能会出现死锁现象。这时, 要么为select加上default,要么为select加上超时时间,要么select不要放在for循环中


Recommend

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK