Go 语言如何从 Channel 读取资料

在这里插入图片描述

Go语言强大的goroutine特性,让各位开发者爱不释手,而多个goroutine如何沟通呢?就是透过Channel来做到。本篇教大家从Channel读取数据的两种方式及使用时机,并实际用一个案例快速了解Channel实作上会遇到哪些问题?底下用两个示例让大家了解如何读取Channel数据出来。

读取Channel两种方式

第一个用的是方式 for range

package main

import "fmt"

func main() {
  ch := make(chan int)
  go func() {
    for i := 0; i < 10; i++ {
      ch <- i
    }
    close(ch)
  }()

  for v := range ch {
    fmt.Println(v)
  }
}

第二种是透过方式 v,ok:= <-ch

package main

import "fmt"

func main() {
  ch := make(chan int)
  go func() {
    for i := 0; i < 10; i++ {
      ch <- i
    }
    close(ch)
  }()

  for {
    v, ok := <-ch
    if !ok {
      return
    }
    fmt.Println(v)
  }
}

看完上面两个示例,开发者也很清楚知道这两种读取方式,但是会遇到什么时候开始用第一种,什么时候该使用第二种?底下来看看一个简单示例

两个goroutine交互读取字元

先看看题目,有一个字串foobar,将字元拆开丢到Channel内,用两个goroutine交互读取字元,底下是最后的输出结果

goroutine01: f
goroutine02: o
goroutine01: o
goroutine02: b
goroutine01: a
goroutine02: r

先把上面题目复制到,大家可以看一下底下示例后,看看怎么写出两个goroutine,以下示例解答:

package main

import (
  "sync"
)

func main() {
  str := []byte("foobar")
  ch := make(chan byte, len(str))
  wg := &sync.WaitGroup{}
  wg.Add(2)

  for i := 0; i < len(str); i++ {
    ch <- str[i]
  }

  go func() {
  }()

  go func() {
  }()

  wg.Wait()
}

看完这题目,大家应该就知道是无法使用方式一来读取channel数据,因为会持续读数据直到channel被关闭为止,这样是不能保证另一个gorountine可以正确读到下一个字元。for range

实作方式

从上面示例可以看到两个goroutine里面写的代码应该要一样,故需要一个channel来通知下一个goroutine进行读取,将代码改成如下:

package main

import (
  "fmt"
  "sync"
)

func main() {
  str := []byte("foobar")
  ch := make(chan byte, len(str))
  next := make(chan struct{})
  wg := &sync.WaitGroup{}
  wg.Add(2)

  for i := 0; i < len(str); i++ {
    ch <- str[i]
  }

  close(ch)

  go func() {
    defer wg.Done()
    for {
      <-next

      v, ok := <-ch
      if ok {
        fmt.Println("goroutine01:", string(v))
      } else {
        close(next)
        return
      }

      next <- struct{}{}
    }
  }()

  go func() {
    defer wg.Done()
    for {
      <-next

      v, ok := <-ch
      if ok {
        fmt.Println("goroutine02:", string(v))
      } else {
        close(next)
        return
      }

      next <- struct{}{}
    }
  }()

  next <- struct{}{}

  wg.Wait()
}
  1. 首先当数据全部写进Channel后,需要关闭Channel
  2. 新增next Channel用来通知下一个goroutine读取数据
  3. main主函式要先丢数据到next Channel
  4. 当ch Channel读取数据结束后,需要关闭next Channel

执行完上述步骤后,会得到底下结果

goroutine02: f
goroutine01: o
goroutine02: o
goroutine01: b
goroutine02: a
goroutine01: r
panic: close of closed channel

这边可以看到此channel被关闭后,会一直有数据,故需要用另一种方式来判断channel是否关闭,就改成如下<-next

 go func() {
    defer wg.Done()
    for {
      stop, ok := <-next
      if !ok {
        return
      }

      v, ok := <-ch
      if ok {
        fmt.Println("goroutine01:", string(v))
      } else {
        close(next)
        return
      }

      next <- stop
    }
  }()

程序可以正确执行了,但是看到代码,我们可以在重构一次if else

 go func() {
    defer wg.Done()
    for {
      stop, ok := <-next
      if !ok {
        return
      }

      v, ok := <-ch
      if !ok {
        close(next)
        return
      }

      fmt.Println("goroutine01:", string(v))
      next <- stop
    }
  }()

最后完整代码如下

package main

import (
  "fmt"
  "sync"
)

func main() {
  str := []byte("foobar")
  ch := make(chan byte, len(str))
  next := make(chan struct{})
  wg := &sync.WaitGroup{}
  wg.Add(2)

  for i := 0; i < len(str); i++ {
    ch <- str[i]
  }

  close(ch)

  go func() {
    defer wg.Done()
    for {
      stop, ok := <-next
      if !ok {
        return
      }

      v, ok := <-ch
      if !ok {
        close(next)
        return
      }

      fmt.Println("goroutine01:", string(v))
      next <- stop
    }
  }()

  go func() {
    defer wg.Done()
    for {
      stop, ok := <-next
      if !ok {
        return
      }

      v, ok := <-ch
      if !ok {
        close(next)
        return
      }

      fmt.Println("goroutine02:", string(v))
      next <- stop
    }
  }()

  next <- struct{}{}

  wg.Wait()
}

心得

透过上述示例希望可以让刚入门朋友了解Channel特性,除了此案例之外,大家可以想一下怎么实现worker pool pattern,之后有机会可以跟大家怎么介绍此部分

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值