golang通道看这篇就够啦

channel-通道

为什么要有通道呢? 通道的作用是解决各个gorountine之间通行的问题;

在开始之前可以先记住一个原则,通道必须和gorountine一起使用,一个直观的体现就是必须要和go关键字搭配使用。 其实也非常好理解,因为如果只有一个gorountine,那么也用不着通信,所以也就用不着通道了。

1. 如何理解通道?

我们可以把通道类比为队列-(先进先出),因此在它内部是可以存值和取值的,但是它有些自己的特点:

  1. 线程安全
  2. 它是一种类型,用chan表示,chan int代表这是一个int型的通道
  3. 既然它是一种类型,那就涉及是引用类型还是值类型,它是引用类型,所以需要make初始化

对于通道的操作来讲,主要就三种操作:

  1. ch<- 10 往通道存值
  2. <-ch 从通道取值
  3. close(ch) 关闭通道

PS:

  • ch这里是一个通道类型的变量名,不是go关键字
  • 如何记忆呢 <-指向通道,则存/否则取值

2. 定义通道

前面说了一大堆,一行代码没看,我们来定义通道,写一段代码看看,前面说了通道是引用类型,因此我们需要通过make初始化通道。

go 复制代码
package main

import "fmt"

func main() {
	ch := make(chan int) // 定义一个装int型的通道

	go func() {
		ch <- 10 // 从通道存
	}()

	fmt.Println(<-ch) // 从通道取
}

上面的代码可以正确运行,会打印出10;我们先不用纠结为是10;我还是继续讨论这里的为啥必须用make初始化,下面不用make看看

go 复制代码
package main

import "fmt"

func main() {
	var ch chan int // 定义一个装int型的通道,通道是引用类型,没有make是nil

	go func() {
		ch <- 10 // 从通道存
	}()

	fmt.Println(<-ch) // 从通道取
	// fatal error: all goroutines are asleep - deadlock! 
}

看到了吧 报错了,如果你是一个初学者,很可能会多次遇到上述错误。

3. 通道的阻塞性

通道在存和取的过程中是存在阻塞的,那么什么时候阻塞呢? 比如:

  1. 从通道中取出一个值<- ch 而此时通道中没有值
  2. 向通道中存入一个值ch<- 10 而此时通道中没有地方可以存

然后我们再回过头来看通道定义中的代码

go 复制代码
func main() {
	ch := make(chan int) // 定义一个装int型的通道

  // goroutinue 是异步的不会阻塞
	go func() {
		ch <- 10 // 向通道中存ch
	}()

  // 但是执行到这里 从通道中取值(<-ch)但是此时通道中并没有值,所以阻塞了
	// 等待前面的go 匿名函数中存入通道后,这里阻塞解除,打印出了10
	fmt.Println(<-ch) // 从通道取
}

上述代码加了注释,希望可以帮助你理解通道中的阻塞。

4. 非缓冲通道- 同步通道

什么是非缓冲通道? 指的是通道中一个元素也不能存下,存入和取出必须同时进行,这有点像两个人A和B,A只能直接把东西交给B,不能放到快递点后,过一段时间B再去取;因此非阻塞通道也叫同步通道。

再定义通道的例子中那就是一个同步通道,打印的时候和存必须同时进行。 我们再来看一个例子

go 复制代码
package main

import "fmt"

func main() {
	ch := make(chan int) // 非缓冲通道 - 同步通道
  
	// 理论上:这里会阻塞 因为ch是一个非缓冲通道,它在等待另一个goruntine 从通道取值
	// 但是这里找不到其它gorountine 取值
	// 所以这里会deadlock报错
	// 这也是为什么说通道都是和go关键字一起使用的原因
	ch <- 10
	fmt.Println(<-ch) // 虽然这里是向通道取值 但是他们不是另外一个gorountine
}

改成下面这样,程序将正常运行

go 复制代码
package main

import "fmt"

func main() {
	ch := make(chan int) // 非缓冲通道 - 同步通道

	// 有go关键字哦
	go func() {
		fmt.Println(<-ch)
	}()

	ch <- 10
}

5. 缓冲通道

经过前面的非缓冲通道的叙述,相信聪明的你,一定能立马清楚什么是缓冲通道: 也就是通道中可以存一定数量的东东,现在在也不用像上面那么一手交钱一手交货啦

我们还是来看一段代码

go 复制代码
package main

import (
	"fmt"
	"time"
)

func main() {
	ch := make(chan int, 6) // 这个通道可以存6个元素

	go func() {
		// 由于从通道中取(range)中有sleep
		// 且由于这里是(缓冲通道) 所以这里会一次将6个值全部存入通道 并不会阻塞
		for i := 0; i < 6; i++ {
			ch <- i
		}

		close(ch) // 存完后关闭通道 不会影响通道的接收
		fmt.Println("6个通道元素全部存完")
	}()

	for i := range ch {
		fmt.Printf("从通道取出值: %d\n", i)
		time.Sleep(time.Second)
	}
}

输出结果如下:

makefile 复制代码
6个通道元素全部存完
从通道取出值: 0
从通道取出值: 1
从通道取出值: 2
从通道取出值: 3
从通道取出值: 4
从通道取出值: 5

6. 单向通道

顾名思义,单向,要么是只能存,要么只能取; 一般是用于函数参数中

看代码

go 复制代码
package main

import (
	"fmt"
)

// ch只能存
func counter(ch chan<- int) {
	for i := 0; i < 6; i++ {
		ch <- i
	}

	close(ch) // 存完后关闭通道(放心不影响取)
}

// ch1只能存
// ch2只能取
func squarer(ch1 chan<- int, ch2 <-chan int) {
	for i := range ch2 {
		ch1 <- i * i
	}

	close(ch1) // ch1存完关闭
}

// ch 只能取
func printer(ch <-chan int) {
	for i := range ch {
		fmt.Println(i)
	}
}

func main() {
	ch1 := make(chan int)
	ch2 := make(chan int)
	go counter(ch1)
	go squarer(ch2, ch1)

	printer(ch2)
}

7.通道取值

主要有两种方式:

  1. range
go 复制代码
for i := range ch{
	// xxx
}
  1. data,ok := ch
go 复制代码
for {
	if data,ok := ch; ok{
		// xxx
	} else {
		break
	}
}

这两种方式都必须要close(ch)

8. close通道

通道和文件不同,创建后通道可以不用写close;它可以被垃圾回收; 但是推荐再发送完后都写下,这样做的一个好处时,可以rangedata,ok取值。

对于close另外一点是,close通道后,即使通道内还有数据,也不影响接收,所以放心大胆的close吧.

相关推荐
乐茵lin40 分钟前
大厂都在问:如何解决map的并发安全问题?三种方法让你对答如流
开发语言·go·编程·map·并发安全·底层源码·sync.map
不会写DN15 小时前
GORM 实战入门:从环境搭建到企业级常用特性全解析
sql·mysql·go·gin
F1FJJ21 小时前
Shield CLI 的 PostgreSQL 插件 v0.5.0 发布:数据库导出 + 协作增强,ER 图全新体验
网络·数据库·docker·postgresql·go
liangbm33 天前
AI-ViewNote:把网课和会议视频自动卷成结构化笔记
ai·typescript·go·软件构建·开源软件·react·桌面软件
我叫黑大帅3 天前
Gin 实战入门:从环境搭建到企业级常用特性全解析
后端·面试·go
我叫黑大帅3 天前
Gin 日志体系详解
后端·面试·go
F1FJJ3 天前
Shield CLI v0.3.3 新增 PostgreSQL 插件:浏览器里管理 PG 数据库
网络·网络协议·docker·postgresql·容器·go
mCell3 天前
【万字长文】从 AI SDK 到 mini-opencode:一次很巧的 Go Agent 架构实践
架构·go·agent
jump_jump3 天前
深入理解 Go Context:从原理到实战(基于 Go 1.26)
go·源码
哈里谢顿4 天前
golang目前遇到的面试题
go