Golang

关注公众号 jb51net

关闭
首页 > 脚本专栏 > Golang > golang channel通道

golang中channel通道的实现

作者:X_PENG

本文主要介绍了golang中channel通道的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

简介-是什么、有什么用

channel字面意思是“通道”,用于goroutine之间进行通信、同步

Goroutine 和 channel 是 Go 语言并发编程的两大基石。Goroutine用于执行并发任务,channel用于 goroutine之间的通信、同步。

看个简单demo:

package main

import (
   "log"
   "time"
)

var logger = log.Default()

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

   go func() {
      logger.Println("go sleep start")
      time.Sleep(3 * time.Second)
      logger.Println("go sleep end, send start")
      c1 <- 1
      logger.Println("send end")
   }()

   logger.Println("main receive start")
   i, ok := <-c1
   logger.Printf("main receive end, i:%d, ok:%t\n", i, ok)
}

运行程序可以看到main协程从通道读取值时会阻塞,直到另一个协程往通道发送数据,才会唤醒main协程。

CSP模型

与主流语言通过共享内存来进行并发控制方式不同,Go 语言采用了 CSP 模型。

共享内存存在竞态问题,需要加锁同步,会造成性能问题。

CSP (Communicating Sequential Process ),即通信顺序进程, 是一种并发编程模型,是一个很强大的并发数据模型,是上个世纪七十年代提出的,用于描述两个独立的并发实体通过共享的通讯 channel(管道)进行通信的并发模型。

强调通信,有这么一句话:

不要通过共享内存来通信,而要通过通信来实现内存共享。

Go语言通过协程Goroutine和通道Channel实现了 CSP 模型。go语言并没有完全实现了CSP模型的所有理论,仅借用了 process和channel这两个概念(分别对应go语言的goroutine和channel):process是并发执行的实体,每个实体之间通过channel通讯来实现数据共享。

channel详解

channel具有如下特性:

channel类型

channel是go语言的一种特殊类型,它是一个引用类型,因此未初始化时其默认零值是nil

  1. 定义channel变量:
var 变量名称 chan 元素类型

例子:

var c1 chan int
var c2 chan string
var c3 chan bool
var c4 chan []int
  1. 初始化channel,使用make

未初始化,默认零值是nil,对nil通道发送或接收都会一直阻塞。

make(chan 元素类型, [缓冲区容量])

例子:

c1 := make(chan int)
c2 := make(chan int, 1)

channel的操作

三种操作:

发送和接收使用<-符号。

发送

ch <- 10 // 把10发送到ch中

发送什么时候会阻塞?
当通道未关闭且缓冲区满时,发送数据就会阻塞,直到有其他协程消费数据使缓冲区不满才不再阻塞。

接收

x := <- ch // 接收并赋值
<-ch       // 仅接收

接收什么时候会阻塞?
当通道未关闭且缓冲区空(无数据可读)时,接收数据就会阻塞,直到有其他协程向通道生产数据使缓冲区不空、有数据可读时才不再阻塞。

多返回值模式,接收操作可以使用多返回值模式:

value, ok := <- ch

注意:接收操作不阻塞,只有两种情况:1. 有数据可读、通道不空;2. 若无数据可读,则通道必须关闭了

个人认为,多返回值模式的作用:如果ok返回false,则表示通道空了且已关闭。

nil通道

chan类型的默认零值是nil,对nil通道进行读、写都会阻塞。

关闭通道

使用close函数

close(ch)

注意:

关闭后的通道有如下特点:

重要:已关闭通道,一定不会阻塞读取操作

  1. 重复关闭会panic

close最佳实践:

  1. 非必要不要close
  2. 比较常见的是将close作为一种通知机制,通过close告诉消费者我关闭了,此时消费者消费就不会再阻塞了。

for range接收通道数据

for range可不断从通道中接收数据,当通道为空且已关闭了,会退出循环(因为为空且已关闭的通道是不可能再有数据了);当通道为空但未关闭,会阻塞直到通道有数据。

示例:

func main() {
   c1 := make(chan int, 3)

   go func() {
      for i := 0; i < 10; i++ {
         <-time.After(time.Second)
         c1 <- i
      }

      <-time.After(5 * time.Second)

      for i := 90; i < 100; i++ {
         <-time.After(time.Second)
         c1 <- i
      }

      <-time.After(5 * time.Second)
      log.Default().Println("---close chan---")
      close(c1)
   }()

   go func() {
      for i := range c1 {
         log.Default().Println(i)
      }
      log.Default().Println("---chan closed---")
   }()

   for {

   }
}

单向通道

是什么?

只能发送或只能接收的通道就是单向通道。

为什么要单向通道?

某些场景下我们想限制使用者对通道的操作:只允许发送或接收,为了表明这种意图并防止被滥用,所以go语言提供了单向channel。

如何使用?

定义单向通道:

箭头<-和关键字chan的相对位置表明了当前通道允许的操作,这种限制将在编译阶段进行检测。

<- chan int // 只接收通道,只能接收不能发送
chan <- int // 只发送通道,只能发送不能接收

「只接收channel」不允许close,因为一般都是发送方来close通道。

通道类型转换:

双向通道可以转成单向通道(是隐式转换),但反之不行。

示例:

生产者只能向channel发送数据,消费者只能从channel接收数据,所以使用单向channel

func main() {
   c1 := make(chan int, 1)
   go producer(c1)
   go consumer(c1)
   for {
   }
}
func producer(ch chan<- int) {
   for i := 0; i < 10; i++ {
      <-time.After(time.Second)
      ch <- i
   }
   log.Default().Println("producer. close chan")
   // 发送通道可close
   close(ch)
}

func consumer(ch <-chan int) {
   for i := range ch {
      log.Default().Println("consume:", i)
      // 接收通过不能close
      //close(ch)
   }
   log.Default().Println("consumer. chan closed")
}

select多路复用

作用:

可以同时监听多个channel的发送或接收操作。

如何使用?

使用方式如下:

select {
case <-ch1:
    //...
case data := <-ch2:
    //...
case ch3 <- 10:    
    //...
default:
    //默认操作
}

示例:

func main() {
   ch := make(chan int, 1)
   for i := 0; i < 10; i++ {
      select {
      case ch <- i:
         log.Default().Println("send")
      case x := <-ch:
         log.Default().Println("receive, x:", x)
      default:
         log.Default().Println("default")
      }

      // 会panic panic: send on closed channel
      //if i == 0 {
      // close(ch)
      //}
   }
}

无缓冲channel和有缓冲channel

make(chan 元素类型, [缓冲区容量])

创建channel时:

无缓冲channel

没有缓冲区,channel中不能缓存数据

有缓冲channel

有缓冲区,channel中可以缓存数据

len和cap方法

对于「无缓冲channel」,len和cap都是0

问题

channel有一个发送方,多个接收方怎么办?

多个接收方共同消费channel中的数据。

示例:

func main() {
   ch1 := make(chan int, 1)
   go consumer(ch1)
   go consumer2(ch1)
   go producer(ch1)
   for {

   }
}
func producer(ch chan<- int) {
   for i := 0; i < 10; i++ {
      <-time.After(time.Second)
      ch <- i
   }
   log.Default().Println("producer. close chan")
   // 发送通道可close
   close(ch)
   log.Default().Println("producer. end")
}

func consumer(ch <-chan int) {
   for i := range ch {
      log.Default().Println("consumer consume:", i)
   }
   log.Default().Println("consumer. chan closed")
}
func consumer2(ch <-chan int) {
   for i := range ch {
      log.Default().Println("consumer2 consume:", i)
   }
   log.Default().Println("consumer2. chan closed")
}

运行结果:

2022/10/30 01:46:46 consumer consume: 0
2022/10/30 01:46:47 consumer2 consume: 1
2022/10/30 01:46:48 consumer consume: 2
2022/10/30 01:46:49 consumer2 consume: 3
2022/10/30 01:46:50 consumer consume: 4
2022/10/30 01:46:51 consumer2 consume: 5
2022/10/30 01:46:52 consumer consume: 6
2022/10/30 01:46:53 consumer2 consume: 7
2022/10/30 01:46:54 consumer consume: 8
2022/10/30 01:46:55 consumer2 consume: 9
2022/10/30 01:46:55 producer. close chan
2022/10/30 01:46:55 producer. end
2022/10/30 01:46:55 consumer. chan closed
2022/10/30 01:46:55 consumer2. chan closed

goroutine泄漏

goroutine泄漏是什么?

是指goroutine一直阻塞而无法被回收。

不正确的使用channel可能导致goroutine泄漏,如下:

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

   go test(ch)
   select {
   case <-ch:
      log.Default().Println("receive")
   case <-time.After(time.Second):
      log.Default().Println("time")
   }

   for {

   }
}

func test(ch chan int) {
   log.Default().Println("test run")
   <-time.After(3 * time.Second)
   ch <- 1
   log.Default().Println("test end")
}

运行结果:

2022/10/30 01:55:17 test run
2022/10/30 01:55:18 time

发生了goroutine泄漏,test方法的goroutine一直阻塞了,因为main gorutine无法接收通道

到此这篇关于golang中channel通道的实现的文章就介绍到这了,更多相关golang channel通道内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文