首页 > 其他分享 >第六章 Channel

第六章 Channel

时间:2022-08-30 21:56:04浏览次数:42  
标签:goroutine go Channel func 第六章 main channel 通道

第六章 Channel

6.1 channel的定义

channel是内置的一种数据类型,用于两个go程中间数据的传输。

image

  1. channel关键字为chan,channel是引用类型,引用类型的变量需要手动分配内存空间,所以需要用make为其创建内存空间,channel初始化的格式如下:
//Type为传输两个gotoutine之间数据的类型
make(chan Type)				//等价于make(chan Type, 0)
make(chan Type, capacity) 	//capacity决定了当前的channel是无缓冲的channel还是有缓冲的channel
  1. 向channel中传输数据的格式:
//1. 向channel中传输数据
channel <- value	//发送value到channel

//2. 向channel中读取数据
<- channel			//从channel中接收数据并将其丢弃
x := <- channel		//从channel中接收数据,将其赋值给x
x, ok := channel	//从channel中接收数据,将其赋值给x,同时检查通道是否已关闭或者是否为空

6.2 channel的使用

下面通过将子go程中的数据传输给main go程的无缓冲channel例子来说明channel的用法:

package main

import "fmt"

func main() {
   //定义一个channel
   c := make(chan int)

   go func() {
      defer fmt.Println("goroutine 结束")

      fmt.Println("goroutine 正在运行...")

      c <- 666 //将“666”发送给c
   }()

   //如果没有“=”左边的“num :=”只有单独的“<- c”,则意为获取c中的数据但并没有使用
   num := <-c //从c中接收数据并赋值给num

   fmt.Println("num = ", num)
   fmt.Println("main goroutine 结束...")
}

控制台输出:

goroutine 正在运行...
goroutine 结束
num =  666
main goroutine 结束...

6.2.1 channel具有同步两个goroutine的能力

当main程和sub程同时执行时,无法保证这两程的执行速度谁快谁慢,下面单独从无缓冲channel来分两种情况讨论,结合上面例子的解释如下:

  1. 当main程快时,其先执行到num := <-c时,sub程还没有执行到c <- 666,此时就无法将c中的值读取出来,那么main程就会发生阻塞,后续当sub程执行到c <- 666后,这个阻塞就会消失,管道c中的数据就顺利传输了。
  2. 当sub程快时,其实也是一个道理,sub程先执行到c <- 666,此时main程还没有执行到num := <-c,c中的666就没有变量来接收(一个go程发出数据没有人接收是会造成死锁的),那么sub程就会发生阻塞,后续当main程执行到num := <-c后,阻塞就会消失,管道c中的数据就顺利传输了。

所以channel将两个go程进行了同步。

image

6.3 无缓冲和有缓冲的channel

  1. 使用无缓冲的通道在goroutine之间同步(此外6.2.1中的channel就是无缓存的channel):

image

文字描述如下:

① 两个goroutine都到达通道,但哪个都没有开始执行发送或者接收。

② 左侧goroutine将它的手伸进了通道,这模拟了向通道发送数据的行为。这时,这个goroutine会在通道中被锁住,知道交换完成。

③ 右侧的 goroutine将它的手放入通道,这模拟了从通道里接收数据。这个goroutine—样也会在通道中被锁住,直到交换完成。

④ 到 ⑤ 两个goroutine进行交换数据。

⑥ 两个goroutine都将它们的手从通道里拿出来,这模拟了被锁住的goroutine得到释放。两个goroutin现在都可以去做其他事情了。

  1. 使用有缓冲的通道(buffered channel)在goroutine之间同步:

image

文字描述如下:

① 右侧的 goroutine正在从通道接收一个值。

② 右侧的这个goroutine独立完成了接收值的动作,而左侧的goroutine正在发送一个新值到通道里。

③ 左侧的goroutine还在向通道发送新值,而右侧的goroutine正在从通道接收另外一个值。这个步骤里的两个操作既不是同步的,也不会互相阻塞。

④ 所有的发送和接收都完成,而通道里还有几个值,也有一些空间可以存更多的值。

只有当channel已经满,再向里面写数据或者当channel为空,从里面取数据才会发生阻塞。结合上图可以这样解释:只有当③中左侧goroutine将数据塞满了通道或者④中右侧goroutine从通道中没有数据可取,才会发生阻塞。

下面通过例子来呈现有缓冲channel:

package main

import (
   "fmt"
   "time"
)

func main() {
   c := make(chan int, 3) //创建带有3容量的channel

   go func() {
      defer fmt.Println("子go程结束...")

      for i := 0; i < 3; i++ {
         c <- i
         fmt.Println("len(c)=", len(c), "cap(c)=", cap(c))
      }
   }()
   time.Sleep(2 * time.Second) //因为无法保证子go程和主go程谁先执行完谁后执行完,所以让程序睡2s保证子go程能将数据能充分填满管道,保证子go程先于main go程执行完

   for i := 0; i < 3; i++ {
      num := <-c
      fmt.Println("num=", num)
   }

   fmt.Println("main go程结束...")
}

控制台输出:

len(c)= 1 cap(c)= 3
len(c)= 2 cap(c)= 3
len(c)= 3 cap(c)= 3
子go程结束...
num= 0
num= 1
num= 2
main go程结束...

6.4 关闭channel的函数close()

close()可以关闭一个channel。

package main

import "fmt"

func main() {
   c := make(chan int)

   go func() {
      for i := 0; i < 5; i++ {
         c <- i
      }

      //close可以关闭一个channel
      //这里必须要关闭,因为主go程中一直在无限获取channel中的数据,而子go程只传递了5个数据给channel,若不手动关闭则会造成主go程的阻塞
      close(c)
   }()

   //若channel里有数据即ok为true,则打印data,否则终止循环
   for {
      if data, ok := <-c; ok { //ok为true则channel为开放状态,为false则为关闭状态
         fmt.Println(data)
      } else {
         break
      }
   }

   fmt.Println("Main finished...")
}

控制台输出:

0
1
2
3
4
Main finished...

关闭channel需要注意的点:

  • channel不像文件一样需要经常去关闭,只有当你确实没有任何发送的数据了,或者你想显式地结束range循环之类,才去关闭channel;
  • 关闭channel后,无法向channel再发送数据(引发panic错误后导致接收立即返回零值);
  • 关闭channel后,可以继续从channel接收数据,比如向channel发送了几个数据,而channel中有缓存,所以可以继续接收里面的数据;
  • 对于nil channel(就是只用var声明了没用make给内存空间的channel),无论收发都会被阻塞。

6.5 range在channel中的使用

6.4中主go程中无限的对channel中数据的获取可以用range进行简化:

package main

import "fmt"

func main() {
   c := make(chan int)

   go func() {
      for i := 0; i < 5; i++ {
         c <- i
      }

      close(c)
   }()

   //对c中的数据进行遍历赋值给data
   for data := range c {
      fmt.Println(data)
   }

   fmt.Println("Main finished...")
}

控制台输出:

0
1
2
3
4
Main finished...

6.6 select在channel中的使用

单流程下一个go只能监控一个channel的状态,select可以完成监控多个channel的状态。

select {
    case <- chan1:
    	//如果chan1成功读到数据,则进行该case处理语句
    case chan2 <- 1:
    	//如果成功向chan2写入数据,则进行该case处理语句
    default:
    	//如果上面都没有成功,则进入default处理流程
}

以上的select监控了chan1是否可读,chan2是否可写,这两种情况谁先触发就会去处理相应的case。一般来说会在select外面包一层for来不断的对各个channel状态进行监测。

斐波那契数列例子说明select的用法:

package main

import "fmt"

func fibonacii(c, quit chan int) {
   x, y := 1, 1

   for {
      select {
      case c <- x: //如果c可写,即c没有阻塞,则case就会进来
         x = y
         y = x + y
      case <-quit: //如果quit可读,则打印quit,表示结束
         fmt.Println("quit")
         return
      }
   }
}

func main() {
   c := make(chan int)
   quit := make(chan int)

   go func() {
      for i := 0; i < 6; i++ {
         fmt.Println(<-c)
      }

      quit <- 0 //当循环结束给quit信道传入一个0值
   }()

   fibonacii(c, quit)
}

控制台输出:

1
1
2
4
8
16
quit

标签:goroutine,go,Channel,func,第六章,main,channel,通道
From: https://www.cnblogs.com/SpriteLee/p/16640993.html

相关文章

  • 大道如青天,协程来通信,Go lang1.18入门精炼教程,由白丁入鸿儒,Go lang通道channel的使
    众所周知,Golang的作用域相对严格,数据之间的通信往往要依靠参数的传递,但如果想在多个协程任务中间做数据通信,就需要通道(channel)的参与,我们可以把数据封装成一个对象,然后把......
  • channel与range、select
    channel与range、selectpackagemainimport"fmt"funcmain(){c:=make(chanint)gofunc(){fori:=0;i<5;i++{c<-i......
  • channel
    channel有缓冲与无缓冲同步问题packagemainimport("fmt""time")funcmain(){c:=make(chanint,3)//带有缓冲的channelfmt.Println("len(c......
  • channel定义与使用
    channel定义与使用packagemainimport"fmt"funcmain(){//定义一个channelc:=make(chanint)gofunc(){deferfmt.Println("goroutine结......
  • 常用Channel
    KafkaChannel生产者a1.channels.c1.type=org.apache.flume.channel.kafka.KafkaChannela1.channels.c1.kafka.bootstrap.servers=hadoop102:9092,hadoop103:9092,......
  • 深入理解JUC:第六章:Semaphore信号灯
    理论:Semaphore是synchronized的加强版,作用是控制线程的并发数量多个线程抢多个资源,下面案例是有六台车抢三个停车位使用Semaphore的代码:publicclassDemo{......
  • go语言并发-02channel
    go语言通道channel如果说goroutine是Go语言程序的并发体的话,那么channels就是它们之间的通信机制。一个channels是一个通信机制,它可以让一个goroutine通过它给......
  • go 语言 channel for select
    示例demo55packagemainimport("fmt""time")funcmain(){intChan:=make(chanint,10)//初始化intchan通道长度10int......
  • netty源码分析之AbstractNioByteChannel.NioByteUnsafe.read()
    1@Override2publicfinalvoidread(){3finalChannelConfigconfig=config();4if(shouldBreakReadReady(conf......
  • 10.java NIO核心2:通道(Channel)
    publicinterfaceChannelextendClonseable{}. 写案例:@Testpublicvoidwrite(){try{//1.字节输出流到目标文件......