这是我纯手写的《Go语言入门》,手把手教你入门Go。源码+文章,看了你就会🥴!
文章中所有的代码我都放到了github.com/GanZhiXiong/go_learning这个仓库中!
看文章时,对照仓库中代码学习效果更佳哦!
通道的种类
通道分为无缓冲通道和有缓冲通道。
实际上,选择有缓冲通道或无缓冲通道将改变应用程序的行为和性能。
区别就是创建的时候是否分配大小
- 无缓存channel
var ch1 = make(chan type)
,未分配大小var ch2 = make(chan type, 0)
,分配大小为0也等同给于未分配大小
- 有缓存channel
ch := make(chan type, size)
,size为分配的大小
无缓冲通道
在前面的文章中,讲的都是无缓冲通道。
无缓冲通道指的是在接收前没有能力保存任何值的通道。
该通道要求发送goroutine和接收goroutine同时准备好,才能完成发送和接收操作。
下图展示了两个goroutine如何利用无缓冲通道来共享一个值:
- 第1步两个goroutine都到达通道
- 第2部左侧的goroutine将它的手伸进通道
这是向通道发送数据的行为。该goroutine会在通道中被锁住。 - 第3步右侧的goroutine将它的手伸进通道
这是从通道接收数据的行为。该goroutine会在通道中被锁住。 - 第4步和第5步进行交换
- 第6步两个goroutine将它们的手从通道拿出来
两个goroutine从锁住变为释放
示例1
下面的代码能很好说明无缓冲通道的使用:
func TestUnbufferedChan1(t *testing.T) {
c := make(chan string)
// 等待一组线程的结束
var wg sync.WaitGroup
// 设定等待完成的协程数
wg.Add(2)
go func() {
// 当前协程执行完成,就是把需要等待的协程数减1
defer wg.Done()
t.Log("下面的代码(发送者)会阻塞")
c <- `foo`
t.Log("发送者停止阻塞")
}()
go func() {
// 当前协程执行完成,就是把需要等待的协程数减1
defer wg.Done()
time.Sleep(time.Second * 2)
t.Log(`Message: ` + <-c)
t.Log("接收者已经收到了")
}()
t.Log("等待协程执行完成")
// 等待协程执行完成
wg.Wait()
t.Log("完成")
}
WaitGroup在24 - 并发编程goroutine这篇文章中有讲到过
示例2
在网球比赛中,两位选手会把球在两个人之间来回传递。选手总是处在以下两种状态之一,要么在等待接球,要么将球打向对方。可以使用两个 goroutine 来模拟网球比赛,并使用无缓冲的通道来模拟球的来回,代码如下所示:
// wg用来等待程序结束
var wg sync.WaitGroup
func TestUnbufferedChan(t *testing.T) {
// 创建一个无缓冲通道
court := make(chan int)
// 计数加2,表示要等待两个goroutine
wg.Add(2)
// 启动两个选手
go player("A", court)
go player("B", court)
// 发球
court <- 1
// 灯带游戏结束
wg.Wait()
}
func player(name string, court chan int) {
defer wg.Done()
fmt.Println(name)
for {
// 等待球被击打过来
ball, ok := <-court
if !ok {
// 如果通道被关闭,我们就赢了
fmt.Printf("Player %s Won\n", name)
return
}
// 这是由于rand是一种伪随机数,你可以通过设定不同seed(种子)的方法来解决这个问题
rand.Seed(time.Now().UnixNano())
// 选随机数,然后用这个数来判断我们是否丢球
n := rand.Intn(100)
if n%13 == 0 {
fmt.Printf("Player %s Missed\n", name)
// 关闭通道,表示我们输了
close(court)
return
}
// 显示击球数,并将击球数加1
fmt.Printf("Player %s Hit %d\n", name, ball)
ball++
// 将球打向对手
court <- ball
}
}
输出结果你自己猜猜先。
示例3
3个协程,一个用于不停打印生成数字,一个负责对生成的数字计算平分,另一个负责打印计算的结果。
分析:只需要两个通道即可,一个用于传数字,一个用传结果。
func TestUnbufferedChan2(t *testing.T) {
naturals := make(chan int)
squares := make(chan int)
// 阻塞主协程
block := make(chan int)
// counter
go func() {
for i := 0; ; i++ {
// 限制下循环速度
time.Sleep(2 * time.Second)
t.Log("counter", "准备发送i")
naturals <- i
t.Log("counter", "已发送i")
}
}()
// squarer
go func() {
for {
t.Log("squarer", "准备接收naturals")
i := <- naturals
t.Log("squarer", "已接收naturals")
t.Log("squarer", "准备发送squares")
squares <- i * i
t.Log("squarer", "已发送squares")
}
}()
// printer
go func() {
for {
t.Log("printer", "准备接收squares")
t.Log(<-squares)
t.Log("printer", "已接收squares")
}
}()
block <- 0
}
示例4
示例3是无限计算,如果我想有限计算3个呢?
这还不简单,直接将无限循环改为有限循环3次即可啊!
// counter
go func() {
for i := 0; i < 3; i++ {
// 限制下循环速度
time.Sleep(2 * time.Second)
t.Log("counter", "准备发送i")
naturals <- i
t.Log("counter", "已发送i")
}
}()
😯 但是计算3次后竟然报错了:fatal error: all goroutines are asleep - deadlock!
原因很简单:
当循环3次跳出循环后,counter函数结束。而squarer和printer这两个协程由于一直接收不到消息而被阻塞,并且主协程也被block这个通道阻塞。程序中所有goroutine都被阻塞了,并且是永远的阻塞了,所以会报错。如果所有的goroutine是由于调用了sleep暂时被阻塞,则不会。
正确的做法是:循环3次后,关闭通道。使用for range来接收,接收后关闭通道。
如果你对关闭通道的使用还不熟悉,请参考我写的27 - 单向通道(chan)里面有讲解关闭通道
func TestUnbufferedChan4(t *testing.T) {
naturals := make(chan int)
squares := make(chan int)
// 阻塞主协程
block := make(chan int)
// counter
go func() {
for i := 0; i < 3; i++ {
// 限制下循环速度
time.Sleep(2 * time.Second)
t.Log("counter", "准备发送i")
naturals <- i
t.Log("counter", "已发送i")
}
close(naturals)
}()
// squarer
go func() {
// 只有naturals关闭后,for range才会结束
for i := range naturals {
t.Log("squarer", "准备发送squares")
squares <- i * i
t.Log("squarer", "已发送squares")
}
close(squares)
}()
// printer
go func() {
for res := range squares {
t.Log("printer", "准备接收squares")
t.Log(res)
t.Log("printer", "已接收squares")
}
close(block)
}()
t.Log("结束", <-block)
}
=== RUN TestUnbufferedChan4
28_unbuffered_chan_test.go:196: counter 准备发送i
28_unbuffered_chan_test.go:198: counter 已发送i
28_unbuffered_chan_test.go:207: squarer 准备发送squares
28_unbuffered_chan_test.go:209: squarer 已发送squares
28_unbuffered_chan_test.go:217: printer 准备接收squares
28_unbuffered_chan_test.go:218: 0
28_unbuffered_chan_test.go:219: printer 已接收squares
28_unbuffered_chan_test.go:196: counter 准备发送i
28_unbuffered_chan_test.go:198: counter 已发送i
28_unbuffered_chan_test.go:207: squarer 准备发送squares
28_unbuffered_chan_test.go:209: squarer 已发送squares
28_unbuffered_chan_test.go:217: printer 准备接收squares
28_unbuffered_chan_test.go:218: 1
28_unbuffered_chan_test.go:219: printer 已接收squares
28_unbuffered_chan_test.go:196: counter 准备发送i
28_unbuffered_chan_test.go:198: counter 已发送i
28_unbuffered_chan_test.go:207: squarer 准备发送squares
28_unbuffered_chan_test.go:209: squarer 已发送squares
28_unbuffered_chan_test.go:217: printer 准备接收squares
28_unbuffered_chan_test.go:218: 4
28_unbuffered_chan_test.go:219: printer 已接收squares
28_unbuffered_chan_test.go:224: 结束 0
--- PASS: TestUnbufferedChan4 (6.01s)
PASS
支持🤟
- 🎸 [关注❤️我吧],我会持续更新的。
- 🎸 [点个👍赞吧],码字不易麻烦了。