「Golang成长之路」并发之Channel下

一、channel(通道)的介绍

如果说goroutine是GO并发的执行体,channel(通道)就是他们的连接。channel是可以让一个goroutine发送特定值到另一个goroutine的通信机制。
「Golang成长之路」并发之channel篇
每一个通道是一个具体的类型,叫做通道的元素类型。如有一个int类型的通道就写成:
chan int
当然这里也可以使用内置函数——make函数来创建一个通道:

ch := make(chan int)  //ch是 'chan int ' 类型

二、 channel的语法

  1. chanDemo()
    创建channel:
    和普通的int、float等类型是一样
    var ch1 chan int         //此时 ch1 == nil
    var ch2 chan float32     //此时 ch2 == nil
    var ch3 chan string      //此时 ch3 == nil
    我们也可以使用内置函数make来创建:
    ch1 := make(chan int)
    ch2 := make(chan float32)
    ch3 := make(chan string)
    package main
    import "fmt"
    func main(){
    ch := make(chan int)  //创建channel
    ch <- 1   //向channel里发数据
    n := <- ch //从channel收数据
    fmt.Println(n) 
    }
    这里会报错:all goroutine air asleep - deadlock!
    原因:channel是用于goroutine和goroutine之间的通信管道,在上面的代码中我们只有一个主goroutine(main)所以没有人来接收ch中的信息,会造成死锁。

这里我们需要启动一个goroutine:

package main

import "fmt"

func chanDemo() {
   ch := make(chan int)
   go func(){  //参见函数式编程:https://learnku.com/articles/59902
      for {
      n := <-ch
         fmt.Println(n)
      }
   }()
   ch <- 100
   ch <- 200
   ch <- 300
  time.Sleep(time.Millisecond) //为了让所有数据输出,需要规定程序运行时间
}
func mian(){
    chanDemo()
}

打印结果为:

100
200
300
  1. channel可作为参数
    在函数式编程中函数是一等公民,函数可作为参数、返回值等
    channel也一样,也可以作为参数,返回值。
package main

import "fmt"

func worker(id int ch chan int ){ //将channel作为参数
    for {
       n := <- ch
       fmt.Printf("worker %d received %d\n",id, n )
    }
}
func chanDemo(){
    ch := make( chan int)
    go worker(0, ch)  //开一个并发
    ch <- 100
    ch <- 200
    ch <- 300
    time.Sleep(time.Millisecond) //为了让所有数据输出,需要规定程序运行时间
}

func main(){
    chanDemo()
}

打印结果为:

worker 0 received 100
worker 0 received 200
worker 0 received 300

这里我们可以随意创建,创建10goroutine:
将chanDemo()改一下

func chanDemo(){
   var channels [10]chan int //创建channel数组
   for i := 0; i < 10; i++{
      channels[i] = make(chan int)
      go worker(i, channels[i]) //创建10个goroutine
   }
   for i := 0; i < 10; i++{
      channels[i] <- 'a' + 1
   }
   time.Sleep(time.Millisecond)
}

打印结果:
worker 4 received 98
worker 0 received 98
worker 1 received 98
worker 2 received 98
worker 9 received 98
worker 6 received 98
worker 3 received 98
worker 8 received 98
worker 5 received 98
worker 7 received 98
* 分析:在修改的chanDemo()中,我们开了10个goroutine,而每一个goroutine都分发了一个channel,从达到每一个goroutine都可以和主goroutine(main)通信。*

  1. channel可作返回值
    同样channel也可以作为返回值
    func creatworker(id int) chan int { 
       c := make(chan int)
       go func() {
         for {
            n := <-c
            fmt.Printf("worker %d received %d\n", id, n)
         }
      }()
      return c
      //在creatworker()中主要是go func()在真正的在做事,c创建后立即被返回
    }
    //
    func chanDemo(){
      var channels [10]chan int //创建channel数组
      for i := 0; i < 10; i++{
         channels[i] = creatworker(i)
      }
      for i := 0; i < 10; i++{
         channels[i] <- 'a' + 1
      }
      time.Sleep(time.Millisecond)
    }
    //
    func main() {
    chanDemo()
    }
    打印结果:
    worker 9 received 98
    worker 8 received 98
    worker 5 received 98
    worker 6 received 98
    worker 0 received 98
    worker 3 received 98
    worker 2 received 98
    worker 1 received 98
    worker 4 received 98
    worker 7 received 98

3.bufferedChannel(缓冲通道)
在前面内容里:

package main
import "fmt"
func main(){
ch := make(chan int)  //创建channel
ch <- 1   //向channel里发数据
   n := <- ch //从channel收数据
fmt.Println(n) 
}

会报错:all goroutine air asleep - deadlock!
是因为没有人去收channel的数据,但是在上面代码中我们发了1,就必须收1,这样比较好资源,所以我们使用缓冲通道,就可有避免死锁了。
我们这样定义:

ch := make(chan int, 3)  //创建一个缓冲容量为3的通道
func bufferedChannel(){
   ch := make(chan int, 3)
   ch <- 1
   ch <- 2
   ch <- 3
}

func main() {
  bufferedChannel()
}

这样run就不会deadlock,当然如果再向ch发数据就会deadlock。
现在仍然使用goroutine来收数据:

func worker(id int,c chan int){
   for {
      n := <-c
      fmt.Printf("worker %d received %c\n", id, n)
   }
}

func bufferedChannel(){
   ch := make(chan int, 3)
   go worker(0, ch)
   for i := 0; i < 10; i++{
   ch <- 'a' + i
   }
  time.Sleep(time.Millisecond)
}

可以看出,只要有人收,缓冲区满了,也不会deadlock。
打印结果:
worker 0 received a
worker 0 received b
worker 0 received c
worker 0 received d
worker 0 received e
worker 0 received f
worker 0 received g
worker 0 received h
worker 0 received i

  1. channelClose
    channel什么时候发完了?
    在前面的代码中,我们知道channel发完了,原因是:我们在main中调用的函数运行结束了,main结束了,程序也就退出了,在并发编程中,我们需要知道数据是什么时候发送结束的。

(1). close()方法

func worker(id int,c chan int){
   for {
      n := <-c
      fmt.Printf("worker %d received %c\n", id, n)
   }
}
func channelClose(){
   ch := make(chan int, 3)
   go worker(0, ch)
   ch <- 'a'
   ch <- 'b'
   ch <- 'c'
   ch <- 'd'
   close(ch)
   time.Sleep(time.Millisecond)
}
func main(){
channelClose()
}

使用Close方法后数据收完后,就一直打印(打印time.Millisecond的时间)空串(或0)
打印结果:
worker 0 received a
worker 0 received b
worker 0 received c
worker 0 received d
worker 0 received
worker 0 received
worker 0 received
worker 0 received
worker 0 received
worker 0 received
worker 0 received
worker 0 received
worker 0 received
……
……
……

(2). n, ok := <- c

func worker(id int,c chan int){
   for {
      n, ok := <- c
      if !ok{
         break
  }
      fmt.Printf("worker %d received %c\n", id, n)
   }
}
func channelClose(){
   ch := make(chan int)
   go worker(0, ch)
   ch <- 'a'
   ch <- 'b'
   ch <- 'c'
   ch <- 'd'
   close(ch)
   time.Sleep(time.Millisecond)
}
func main() {
 channelClose()
}

打印结果:
worker 0 received a
worker 0 received b
worker 0 received c
worker 0 received d

(3). range

func worker(id int,c chan int){
   for n := range c{
      fmt.Printf("worker %d received %c\n", id, n)
   }
}
func channelClose(){
   ch := make(chan int)
   go worker(0, ch)
   ch <- 'a'
   ch <- 'b'
   ch <- 'c'
   ch <- 'd'
   close(ch)
   time.Sleep(time.Millisecond)
}
func main() {
 channelClose()
}

打印结果:
worker 0 received a
worker 0 received b
worker 0 received c
worker 0 received d

本作品采用《CC 协议》,转载必须注明作者和本文链接
刻意学习
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!
未填写
文章
118
粉丝
89
喜欢
173
收藏
246
排名:365
访问:2.6 万
私信
所有博文
社区赞助商