文章目录
- 介绍
- 会报错的几种情况
- 示例
- 缓冲通道(处理同步) -- 串行
- 无缓冲通道(配合 goroutine 处理异步) -- 并行
- 取值
- 高级用法
- 监控通道示例
- WaitGroup 的使用
- 多路复用
- 特点
- 示例
- 单向通道
介绍
-
channel
是一种引用类型, 需要使用make
初始化 - 根据
make
初始化时是否声明容量, 分为缓冲通道
和无缓冲通道
- 遵循先入先出, 保证数据顺序
-
goroutine
间的通信一定要通过channel
, 所以goroutine
执行的方法不能有返回值, 因为相当于main
函数的goroutine
和 自己启动的goroutine
进行通信
会报错的几种情况
- 有缓冲通道开启后, 不能发送和接收超过容量的数据
- 有缓冲通道关闭后, 不得发送数据
- 无缓冲通道开启后, 需要一方先开启 goroutine 等待; 如果发送和接收都开启了 goroutine, 则需要 sync.WaitGroup
- 无缓冲通道关闭后, 不得操作通道
示例
缓冲通道(处理同步) – 串行
package main
import (
"fmt"
)
// 创建 chan
var c = make(chan int, 10)
// 向通道发送数据
func send(n int) {
for i := 0; i < n; i++ {
c <- i
}
}
// 从通道中取出数据
func recv() {
for v := range c {
fmt.Println(v)
}
}
func main() {
// 发送 10 个数
send(10)
// 发送完成,关闭通道
close(c)
// 接收数据
recv()
}
无缓冲通道(配合 goroutine 处理异步) – 并行
package main
import (
"fmt"
)
// 创建 chan
var c = make(chan int)
// 向通道发送数据
func send(n int) {
for i := 0; i < n; i++ {
c <- i
}
}
// 从通道中取出数据
func recv() {
for v := range c {
fmt.Println(v)
}
}
func main() {
// 与缓冲通道不同的是, 这里先准备接收数据, 否则发送时会报错
go recv()
// 发送 10 个数
send(10)
// 本例中是否 close() 并不影响结果, 因为发送完成后, 主进程即结束
close(c)
}
取值
// 直接取值
x := <- C
// **建议使用** for range 循环取值(取值时保证通道已经关闭)
for y := range C {
fmt.Println(y)
}
// for 死循环取值
// 从已经关闭的通道中取出为零值, 但如果存入的就是零值使用这个则会有问题
for {
// 当取到零值时, ok 为 false
r, ok := <-x
if !ok {
break
}
fmt.Println(r)
}
高级用法
监控通道示例
package main
import (
"fmt"
"sync"
)
var (
wg sync.WaitGroup
dataChan chan int // 用于检查的通道
checkChan chan int // 用于存放数据的通道
sendNum = 3 // 并发发送数据的协程数量
recvNem = 2 // 并发接收数据的协程数量
)
// 发送数据
func send(d, c chan int) {
// 模拟存放 5 条数据
for i := 0; i < 5; i++ {
d <- i
}
// 存放数据完成后, 向 checkChan 存放一条数据, 表示该 goroutine 完成工作
c <- 1
// 并发计数器 -1
wg.Done()
}
// 接收数据
func recv(d chan int) {
// 因为 check 函数中已经通过判断关闭了 dataChan
// 这里使用 for range 读取 dataChan 中的数据
for v := range d {
fmt.Println(v)
}
// 并发计数器 -1
wg.Done()
}
// 检查
func check(c, d chan int, n int) {
// 计数器
// 每当并发存数据的操作完成一个, 就会存入一个数据
// 所以当数量 = 并发数量时, 就是数据存放完毕, 就可以关闭数据通道了
count := 0
for {
<-c
count++
if count == n {
close(d) // 关闭通道
wg.Done() // 完成 wg
break // 跳出循环
}
}
}
func main() {
// 通道的初始化
checkChan = make(chan int)
dataChan = make(chan int)
// 开启一个用于检查的通道
wg.Add(1)
go check(checkChan, dataChan, sendNum)
// 并发发送数据
for i := 0; i < sendNum; i++ {
wg.Add(1)
go send(dataChan, checkChan)
}
// 并发接收数据
for i := 0; i < recvNem; i++ {
wg.Add(1)
go recv(dataChan)
}
wg.Wait()
}
WaitGroup 的使用
package main
// 这种写法意思是, 起两个 goroutine, 都执行完成之后, 再继续执行后边的代码
import (
"fmt"
"sync"
)
var (
c = make(chan int)
wg sync.WaitGroup
)
func send(x chan int, y int) {
wg.Add(1)
defer wg.Done()
for i := 0; i < y; i++ {
x <- i
}
close(x)
}
func recv(x chan int) {
wg.Add(1)
defer wg.Done()
for r := range x {
fmt.Println(r)
}
}
func main() {
go send(c, 10)
go recv(c)
wg.Wait()
}
多路复用
特点
- 同时从多个通道取值, 类似switch
- 多个case同时满足则随机取, 没有case满足则会根据最外层for循环处理
示例
- 例1
// 如果单独使用, 则表示等待
select{}
- 例2
package main
// 先从通道 x 中接收值, 如果接收不到就从通道 y 中接收值
import (
"fmt"
"time"
)
func send1(c chan string, n int) {
for i := 0; i < n; i++ {
c <- fmt.Sprintf("f1: %d", i)
}
}
func send2(c chan string, n int) {
for i := 0; i < n; i++ {
c <- fmt.Sprintf("f2: %d", i)
}
}
func main() {
// 这里的通道有没有容量都可以
// 如果通道容量足够, 则可以一次放入, 然后全部取出
// 如果通道容量不够, 则需要依次放入, 依次取出
x := make(chan string)
y := make(chan string)
go send1(x, 100)
go send2(y, 100)
for {
select {
case r := <-x:
fmt.Println(r)
case r := <-y:
fmt.Println(r)
default:
fmt.Println("取不到值")
time.Sleep(time.Second)
}
}
}
- 例3
package main
// 向通道发送数据, 如果发送失败, 则取出通道中的数据
import "fmt"
func main() {
// 重点: 容量为1
x := make(chan int, 1)
// 重点: for循环10次
for i := 0; i < 10; i++ {
select {
// 先尝试向 x 中发送数据
// 发送成功, 不需要其他处理, 此时通道中有数据, 不能再存入其他数据
case x <- i:
// 上一次循环, 通道中容量已满, 执行这个 case, 将通道中数据取出并打印
case r := <-x:
fmt.Println(r)
}
}
}
单向通道
package main
// 用来限制通道的使用
// <- chan 只能从通道接收数据
// chan<- 只能向通道发送数据
import "fmt"
func send(c chan<- int) {
// c是一个单向通道, 只存数据
for i := 0; i < 10; i++ {
c <- i
}
r := <-c
fmt.Println(r)
// 发送完成, 关闭
close(c)
}
func recv(c <-chan int) {
// c是一个单向通道, 只取数据
for r := range c {
fmt.Println(r)
}
}
func main() {
c := make(chan int, 10)
send(c)
recv(c)
}