[系列] Go - chan 通道

概述

原来分享的基础语法的时候,还未分享过 chan 通道,这次把它补上。

chan 可以理解为队列,遵循先进先出的规则。

在说 chan 之前,咱们先说一下 go 关键字。

在 go 关键字后面加一个函数,就可以创建一个线程,函数可以为已经写好的函数,也可以是匿名函数。

举个例子:


   
   
  1. func main() {

  2. fmt.Println("main start")

  3. go func() {

  4. fmt.Println("goroutine")

  5. }()

  6. fmt.Println("main end")

  7. }

输出:


   
   
  1. main start

  2. main end

为什么没有输出 goroutine ?

首先,我们清楚 Go 语言的线程是并发机制,不是并行机制。

那么,什么是并发,什么是并行?

并发是不同的代码块交替执行,也就是交替可以做不同的事情。

并行是不同的代码块同时执行,也就是同时可以做不同的事情。

举个生活化场景的例子:

你正在家看书,忽然电话来了,然后你接电话,通话完成后继续看书,这就是并发,看书和接电话交替做。

如果电话来了,你一边看书一遍接电话,这就是并行,看书和接电话一起做。

说回上面的例子,为什么没有输出 goroutine ?

main 函数是一个主线程,是因为主线程执行太快了,子线程还没来得及执行,所以看不到输出。

现在让主线程休眠 1 秒钟,再试试。


   
   
  1. func main() {

  2. fmt.Println("main start")

  3. go func() {

  4. fmt.Println("goroutine")

  5. }()

  6. time.Sleep(1 * time.Second)

  7. fmt.Println("main end")

  8. }

输出:


   
   
  1. main start

  2. goroutine

  3. main end

这就对了。

接下来,看看如何使用 chan 。

声明 chan


   
   
  1. // 声明不带缓冲的通道

  2. ch1 := make(chan string)

  3. // 声明带10个缓冲的通道

  4. ch2 := make(chan string, 10)

  5. // 声明只读通道

  6. ch3 := make(<-chan string)

  7. // 声明只写通道

  8. ch4 := make(chan<- string)

注意:

不带缓冲的通道,进和出都会阻塞。

带缓冲的通道,进一次长度 +1,出一次长度 -1,如果长度等于缓冲长度时,再进就会阻塞。

写入 chan


   
   
  1. ch1 := make(chan string, 10)

  2. ch1 <- "a"


读取 chan


   
   
  1. val, ok := <- ch1

  2. // 或

  3. val := <- ch1


关闭 chan


   
   
  1. close(chan)

注意:

  • close 以后不能再写入,写入会出现 panic

  • 重复 close 会出现 panic

  • 只读的 chan 不能 close

  • close 以后还可以读取数据


示例


   
   
  1. func main() {

  2. fmt.Println("main start")

  3. ch := make(chan string)

  4. ch <- "a" // 入 chan

  5. go func() {

  6. val := <- ch // 出 chan

  7. fmt.Println(val)

  8. }()

  9. fmt.Println("main end")

  10. }

输出:


   
   
  1. main start

  2. fatal error: all goroutines are asleep - deadlock!

What ? 这是为啥,刚开始就出师不利呀?

因为,定义的是一个无缓冲的 chan,赋值后就陷入了阻塞。

怎么解决它?

声明一个有缓冲的 chan。


   
   
  1. func main() {

  2. fmt.Println("main start")

  3. ch := make(chan string, 1)

  4. ch <- "a" // 入 chan

  5. go func() {

  6. val := <- ch // 出 chan

  7. fmt.Println(val)

  8. }()

  9. fmt.Println("main end")

  10. }

输出:


   
   
  1. main start

  2. main end

为啥没有输出 a , 和前面一样,主线程执行太快了,加个休眠 1 秒钟,再试试。


   
   
  1. func main() {

  2. fmt.Println("main start")

  3. ch := make(chan string, 1)

  4. ch <- "a" // 入 chan

  5. go func() {

  6. val := <- ch // 出 chan

  7. fmt.Println(val)

  8. }()

  9. time.Sleep(1 * time.Second)

  10. fmt.Println("main end")

  11. }

输出:


   
   
  1. main start

  2. a

  3. main end

这就对了。

再看一个例子:


   
   
  1. func main() {

  2. fmt.Println("main start")

  3. ch := make(chan string)

  4. go func() {

  5. ch <- "a" // 入 chan

  6. }()

  7. go func() {

  8. val := <- ch // 出 chan

  9. fmt.Println(val)

  10. }()

  11. time.Sleep(1 * time.Second)

  12. fmt.Println("main end")

  13. }

输出:


   
   
  1. main start

  2. a

  3. main end

再看一个例子:


   
   
  1. func producer(ch chan string) {

  2. fmt.Println("producer start")

  3. ch <- "a"

  4. ch <- "b"

  5. ch <- "c"

  6. ch <- "d"

  7. fmt.Println("producer end")

  8. }

  9. func main() {

  10. fmt.Println("main start")

  11. ch := make(chan string, 3)

  12. go producer(ch)

  13. time.Sleep(1 * time.Second)

  14. fmt.Println("main end")

  15. }

输出:


   
   
  1. main start

  2. producer start

  3. main end

带缓冲的通道,如果长度等于缓冲长度时,再进就会阻塞。

再看一个例子:


   
   
  1. func producer(ch chan string) {

  2. fmt.Println("producer start")

  3. ch <- "a"

  4. ch <- "b"

  5. ch <- "c"

  6. ch <- "d"

  7. fmt.Println("producer end")

  8. }

  9. func customer(ch chan string) {

  10. for {

  11. msg := <- ch

  12. fmt.Println(msg)

  13. }

  14. }

  15. func main() {

  16. fmt.Println("main start")

  17. ch := make(chan string, 3)

  18. go producer(ch)

  19. go customer(ch)

  20. time.Sleep(1 * time.Second)

  21. fmt.Println("main end")

  22. }

输出:


   
   
  1. main start

  2. producer start

  3. producer end

  4. a

  5. b

  6. c

  7. d

  8. main end

就到这吧。

推荐阅读

gRPC

Gin 框架

基础篇

本文欢迎转发,转发请注明作者和出处,谢谢!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值