Go学习之Channel总结

Channel是Go中的一个核心类型,你能够把它当作一个管道,经过它并发核心单元就能够发送或者接收数据进行通信(communication)。面试

类型

T表示任意的一种类型并发

  • 双向: chan T
  • 单向仅发送: chan <-
  • 单向仅接受: <- chan

单向的channel,不只能够经过声明make(chan <- interface{}) 来建立,还能够经过隐身或显示的经过 chan 来转换,以下函数

func main() {
      channel := make(chan int, 10)
        convert(channel)
}
func convert(channel chan<- int) {}

convert函数中,就能够吧channel当成单向输入管道来使用了code

既然 双向 chan,既能够接收,也能够发送,为何还会有单向chan的存在? 个人一个理解即是 权限收敛,例如一个爬虫系统中,有些进程a仅仅负责抓取页面内容,并转发给进程b,那进程a仅须要 单向发送的chan 便可进程

Blocking

缺省状况下,发送chan或接收chan会一直阻塞着,直到另外一方准备好。这种方式能够用来在gororutine中进行同步,而没必要使用显示的锁或者条件变量。同步

如官方的例子中x, y := <-c, <-c这句会一直等待计算结果发送到channel中。如下面例子看一下io

func bufferChannel() {
    channel := make(chan int)
    i := 0
    go func(i int) {
    fmt.Printf("start goroutine %d\n", i)①
        channel <- i
        fmt.Printf("send %d to channel\n", i)②
    }(i)
    time.Sleep(2 * time.Second)
    fmt.Println("sleep 2 second")
    value := <-channel③
    fmt.Println("got ", value)
}

输出结果以下class

start goroutine 0
sleep 2 second
got  0
send 0 to channel

能够看出,go func 执行到了①后并无继续执行②,而是等待③执行完成后,再去执行②,也就能够说明 channel <- i 阻塞了goroutine的继续执行变量

若是,我不想在这里阻塞,而是我直接把数据放到channel里,等接收方准备好后,到channel中自取自用如何处理,这里就涉及到了另外一个概念 buffered channelsed

buffered channel

咱们把程序修改一下

func bufferChannel() {
    channel := make(chan int, 1) // 这里加了个参数
    i := 0
    go func(i int) {
        fmt.Printf("start goroutine %d\n", i)①
        channel <- i
        fmt.Printf("send %d to channel\n", i)②
    }(i)
    time.Sleep(2 * time.Second)
    fmt.Println("sleep 2 second")
    value := <-channel③
    fmt.Println("got ", value)
}

输出结果

start goroutine 0
send 0 to channel
sleep 2 second
got  0

咱们发现go func执行完①以后就执行了②,并无等待③的执行结束,这就是buffered channel的效果了

咱们只须要在make的时候,声明底2个参数,也就是chan的缓冲区大小便可

经过上面的程序能够看出,咱们一直在使用③的造成,即<- chan来读取chan中的数据,可是若是有多个goroutine在同时像一个chan写数据,咱们除了使用

for {
    value <- chan
}

还有什么更优雅的方式吗

for … range

仍是上面那个程序,咱们使用 for … range 进行一下改造

func bufferChannel() {
   channel := make(chan int, 1)
   i := 0
   go func(i int) {
      fmt.Printf("start goroutine %d\n", i)
      channel <- i
      fmt.Printf("send %d to channel\n", i)
   }(i)
   time.Sleep(2 * time.Second)
   fmt.Println("sleep 2 second")
   for value := range channel {
      fmt.Println("got ", value)
   }
}

这样就能够遍历 channel 中的数据了,可是咱们在运行的时候就会发现,哎 这个程序怎么停不下来了?range channel产生的迭代值为Channel中发送的值,它会一直迭代直到channel被关闭,因此 咱们goroutine发送完数据后,把channel关闭一下试试,这一次,咱们再也不进行time.Sleep(2 * time.Second)

func bufferChannel() {
   channel := make(chan int, 1)
   i := 0
   go func(i int) {
      fmt.Printf("start goroutine %d\n", i)
      channel <- i
      fmt.Printf("send %d to channel\n", i)
      close(channel)
   }(i)
   for value := range channel {
      fmt.Println("got ", value)
   }
}

这样,整个程序就能够正常退出了,因此,在使用range的时候须要注意,若是channel不关闭,则range会一直阻塞在这里的

select

咱们上面讲的一直都是只有一个channel的时候,咱们应该怎么去作,加入有两个channel或者更多的channel,咱们应该怎么去作,这里就介绍一下 go里面的多路复用 select,如下面程序为例

func example() {
    tick := time.Tick(time.Second)
    after := time.After(3 * time.Second)
    for {
        select {
        case <-tick:
            fmt.Println("tick 1 second")
        case <-after:
            fmt.Println("after 3 second")
            return
    default:
            fmt.Println("come into default")
            time.Sleep(500 * time.Millisecond)
        }
    }
}

time.Tick是go的time包提供的一个定时器的一个函数,它返回一个channel,并在指定时间间隔内,向channel发送一条数据,time.Tick(time.Second)就是每秒钟向这个channel发送一个数据

time.After是go的time包提供的一个定时器的一个函数,它返回一个channel,并在指定时间间隔后,向channel发送一条数据,time.After(3 * time.Second)就是3s后向这个channel发送一个数据

输出结果

come into default
come into default
tick 1 second
come into default
come into default
tick 1 second
come into default
come into default
tick 1 second
after 3 second

能够看到,select会选择一个没有阻塞的 channel,并执行响应 case下的逻辑,这样就能够避免因为一个 channel阻塞而致使后续的逻辑阻塞的状况了

咱们继续作个小实验,把上面关闭的channel放到 select里面试一下

func example() {
    tick := time.Tick(time.Second)
    after := time.After(3 * time.Second)
    channel := make(chan int, 1)
    go func() {
        channel <- 1
        close(channel)
    }()
    for {
        select {
        case <-tick:
            fmt.Println("tick 1 second")
        case <-after:
            fmt.Println("after 3 second")
            return
        case value := <- channel:
            fmt.Println("got", value)
        default:
            fmt.Println("come into default")
            time.Sleep(500 * time.Millisecond)
        }
    }
}

输出结果

.
.
.
.
got 0
got 0
got 0
got 0
got 0
after 3 second

简直是车祸现场,幸亏设置了3s主动退出,那case的时候,有没有办法判断这个channel是否关闭了呢,固然是能够的,看下面的程序

func example() {
    tick := time.Tick(time.Second)
    after := time.After(3 * time.Second)
    channel := make(chan int, 1)
    go func() {
        channel <- 1
        close(channel)
    }()
    for {
        select {
        case <-tick:
            fmt.Println("tick 1 second")
        case <-after:
            fmt.Println("after 3 second")
            return
        case value, ok := <- channel:
            if ok {
                fmt.Println("got", value)
            } else {
                fmt.Println("channel is closed")
                time.Sleep(time.Second)
            }
        default:
            fmt.Println("come into default")
            time.Sleep(500 * time.Millisecond)
        }
    }
}

输出结果

come into default
got 1
channel is closed
tick 1 second
channel is closed
channel is closed
after 3 second

综上能够看出,经过 value, ok := <- channel 这种形式,ok获取的就是用来判断channel

是否关闭的,ok为 true,表示channel正常,不然,channel就是关闭的

相关文章
相关标签/搜索