文章目录

  • 介绍
  • 会报错的几种情况
  • 示例
  • 缓冲通道(处理同步) -- 串行
  • 无缓冲通道(配合 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)

}