上一篇记录了goroutine操作共享数据时保证对共享资源的安全访 问以及消除竞争状态
原子操作 互斥锁Channel 也是Go语言里的一种引用类型,通道可以被认为是Goroutines通信的管道。类似于管道中的水从一端到另一端的流动,数据可以从一端发送到另一端,通过通道接收。
当一个资源需要在 goroutine 之间共享时,通道在 goroutine 之间架起了一个管道,并提供了 确保同步交换数据的机制。声明通道时,需要指定将要被共享的数据的类型。可以通过通道共享 内置类型、命名类型、结构类型和引用类型的值或者指针。
无缓冲的通道保证同时交换数据,而有缓冲的通道不做这种保证
使用 make函数 和关键词 chan创建通道,使用 <- 运算符 进行赋值和读取操作
//创建 无缓冲通道 整型
unbuffer := make(chan int)
//创建 有缓冲通道 字符串型
buffer := make(chan string, 10)
fmt.Printf("%T=====%T",unbuffer,buffer)
//chan int=====chan string通道收到了 test1
buffer <- "test1"
//从通道接收字符串
value := <-buffer
fmt.Println("通道收到了",value)//通道收到了 test1
无缓冲通道
无缓冲的通道(unbuffered channel)是指在接收前没有能力保存任何值的通道。这种类型的通 道要求发送 goroutine 和接收 goroutine 同时准备好,才能完成发送和接收操作。如果两个 goroutine 没有同时准备好,通道会导致先执行发送或接收操作的 goroutine 阻塞等待。这种对通道进行发送 和接收的交互行为本身就是同步的。其中任意一个操作都无法离开另一个操作单独存在。
func unbufferTest(){
court := make(chan int)
wg.Add(2)
go play("张三",court)
go play("李四", court)
//发球
court<-1
wg.Wait()
}
//模拟选手打球
func play(name string, court chan int) {
defer wg.Done()
for {
//等待球打过来
ball,ok := <-court
if !ok{
//如果通道关闭则我们赢了
fmt.Println("winner is ",name)
return
}
n := rand.Intn(100)
if n%5 == 0{
fmt.Println("missied ",name)
close(court)
return
}
fmt.Printf("选手 %s 打出了第%d次球\n",name,ball)
ball++
//打球给对方
court<-ball
}
}
输出
选手 李四 打出了第1次球
选手 张三 打出了第2次球
missied 李四
winner is 张三
有缓冲通道
有缓冲的通道(buffered channel)是一种在被接收前能存储一个或者多个值的通道。这种类 型的通道并不强制要求 goroutine 之间必须同时完成发送和接收。通道会阻塞发送和接收动作的 条件也会不同。只有在通道中没有要接收的值时,接收动作才会阻塞。只有在通道没有可用缓冲 区容纳被发送的值时,发送动作才会阻塞。这导致有缓冲的通道和无缓冲的通道之间的一个很大 的不同:无缓冲的通道保证进行发送和接收的 goroutine 会在同一时间进行数据交换;有缓冲的 通道没有这种保证。
package main
import (
"fmt"
"math/rand"
"sync"
"time"
)
/**
* 有缓冲通道
*/
var wg sync.WaitGroup
const (
//开启的goroutine数量
numberGoroutine = 4
//要处理的工作的数量
taskLoad = 10
)
func init() {
// 初始化随机数种子
rand.Seed(time.Now().UnixNano())
}
func main() {
//createChan()
bufferTest()
}
func bufferTest() {
tasks := make(chan string, taskLoad)
wg.Add(numberGoroutine)
for line := 1; line <= numberGoroutine; line++ {
go work(tasks, line)
}
addWork(tasks)
close(tasks)
wg.Wait()
}
//增加工作
func addWork(tasks chan string) {
for i := 1; i <= taskLoad; i++ {
tasks <- fmt.Sprintf("工作任务%d", i)
}
}
//模拟选手打球
func work(tasks chan string, worker int) {
defer wg.Done()
for {
//等待球打过来
task, ok := \n", worker, task)
}
}
输出:
工人4开始干活了,干的是工作任务1
工人2开始干活了,干的是工作任务2
工人3开始干活了,干的是工作任务3
工人1开始干活了,干的是工作任务4
工人4 完成了工作任务《工作任务1》>
工人4开始干活了,干的是工作任务5
工人4 完成了工作任务《工作任务5》>
工人4开始干活了,干的是工作任务6
工人1 完成了工作任务《工作任务4》>
工人1开始干活了,干的是工作任务7
工人2 完成了工作任务《工作任务2》>
工人2开始干活了,干的是工作任务8
工人1 完成了工作任务《工作任务7》>
工人1开始干活了,干的是工作任务9
工人4 完成了工作任务《工作任务6》>
工人4开始干活了,干的是工作任务10
工人2 完成了工作任务《工作任务8》>
工作结束了
工人1 完成了工作任务《工作任务9》>
工作结束了
工人3 完成了工作任务《工作任务3》>
工作结束了
工人4 完成了工作任务《工作任务10》>
工作结束了
第 46 行中关闭通道的代码非常重要。当通道关闭后,goroutine 依旧可以从通道接收数据, 但是不能再向通道
里发送数据