目录
一、简介
channel 是 Go中最核心的功能之一,因此理解 channel 的原理对于学习和使用 Go 非常重要。 channel 是 goroutine 之间通信的一种方式,可以类比成 Unix 中进程管道通信方式,channel 是支撑 Go 语言高性能并发编程模型的重要结构。
通道像一个传送带或者队列,总是遵循先进先出(First In First Out)的规则,保证收发数据的顺序。在任何时候,同时只能有一个 goroutine 访问通道进行发送和获取数据。
二、数据结构
// channel 数据结构
type hchan struct {
qcount uint // 当前队列中剩余元素个数
dataqsiz uint // 环形队列长度,即可以存放的元素个数
buf unsafe.Pointer // 环形队列指针
elemsize uint16 // 每个元素的大小
closed uint32 // 标识关闭状态
elemtype *_type // 元素类型
sendx uint // 队列下标,指示元素写入时存放到队列中的位置
recvx uint // 队列下标,指示元素从队列的该位置读出
recvq waitq // 等待读消息的 goroutine 队列
sendq waitq // 等待写消息的 goroutine 队列
lock mutex // 互斥锁,chan 不允许并发读写
}
三、菜鸟实战
实战需求:
用程序实现一个函数,原型为:void printNumber(int N),
内部创建三个线程 A、B、C,三个线程交替输出 1 到 N 之间的数字,
A 线程输出 1、4、7…,B 线程 2、5、8…,C 线程输出 3、6、9…,
马上安排!
1、创建 g009.go
/*
* @Author: 菜鸟实战
* @FilePath: /go110/go-009/g009.go
* @Description: channel
*/
package main
import (
"fmt"
"runtime"
"sync/atomic"
)
var index int32
var maxNum int32
// 开 ABC 三个channel 分别用于三个 channel 监听数据
var chA = make(chan int32)
var chB = make(chan int32)
var chC = make(chan int32)
// 监听关闭
var chClose = make(chan int32)
type Values struct {
m map[string]string
}
func (v Values) Get(key string) string {
return v.m[key]
}
func printNumber(N int) {
//开三个协程做任务
go printA(N)
go printB(N)
go printC(N)
//给chA发数据通知它开始打印
chA <- 1
//等待
<-chClose
close(chA)
close(chB)
close(chC)
close(chClose)
fmt.Println("done")
}
func printA(N int) {
//开始监听
for {
select {
case v := <-chA:
atomic.AddInt32(&index, 1)
fmt.Print(v)
fmt.Print("A,")
chB <- v + 1 //通知B开始打印
if v == maxNum {
chClose <- 1
return
}
case <-chClose:
return
}
}
}
func printB(N int) {
//B开始监听
for {
select {
case v := <-chB:
atomic.AddInt32(&index, 1)
fmt.Print(v)
fmt.Print("B,")
chC <- v + 1 // 通知C开始打印
if v == maxNum {
chClose <- 1
return
}
case <-chClose:
return
}
}
}
func printC(N int) {
//开始监听
for {
select {
case v := <-chC:
atomic.AddInt32(&index, 1)
fmt.Print(v)
fmt.Print("C,")
chA <- v + 1 // 通过 channel 通知 A
if v == maxNum {
chClose <- 1
return
}
case <-chClose:
return
}
}
}
func main() {
// 使用内置函数打印
println("Hello", "菜鸟实战")
index = 0
maxNum = 20
printNumber(20)
// 当前版本
fmt.Printf("版本: %s \n", runtime.Version())
}
2、编译和运行
# 1、生成模块依赖
go mod init g009
# 2、编译
go build g009.go
# 3、编译后的目录结构
└── go-009
├── g009
├── g009.go
└── go.mod
# 4、运行
go run g009
3、运行结果
Hello 菜鸟实战
1A,2B,3C,4A,5B,6C,7A,8B,9C,10A,11B,12C,13A,14B,15C,16A,17B,18C,19A,20B,done
21C,版本: go1.17.10
菜鸟实战,持续学习!