interview-go
interview-go copied to clipboard
交替打印数字和字母 - 示例问题描述及优化
示例代码:
letter,number := make(chan bool),make(chan bool)
wait := sync.WaitGroup{}
go func() {
i := 1
for {
select {
case <-number:
fmt.Print(i)
i++
fmt.Print(i)
i++
letter <- true
break
default:
break
}
}
}()
wait.Add(1)
go func(wait *sync.WaitGroup) {
str := "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
i := 0
for{
select {
case <-letter:
if i >= strings.Count(str,"")-1 {
wait.Done()
return
}
fmt.Print(str[i:i+1])
i++
if i >= strings.Count(str,"") {
i = 0
}
fmt.Print(str[i:i+1])
i++
number <- true
break
default:
break
}
}
}(&wait)
number<-true
wait.Wait()
这个代码正确得到了结果,但是代码它本身存在严重的瑕疵:
- (重要)接收信号使用了 select default 模式,
chan没有数据时,陷入多余的循环,浪费硬件资源 - (重要)没有正确关闭 number 与 letter 通道,这个简短的程序它运行没有问题,是等待 main 函数退出系统回收相关资源的
- (重要)打印 number 的 goroutine 没有正确关闭,是等待 main 函数退出系统回收
- (次要)低效率的字典模式打印字符串,需要预先创建需要打印字符的字典,然后按字典进行打印
交替打印数字和字母
问题描述
使用两个 goroutine 交替打印序列,一个 goroutine 打印数字, 另外一个 goroutine 打印字母, 最终效果如下:
12AB34CD56EF78GH910IJ1112KL1314MN1516OP1718QR1920ST2122UV2324WX2526YZ2728
考点
对 goroutine 状态控制,执行顺序控制, 对 chan 的应用, 以及正确的退出程序。
解题思路
问题很简单,使用 channel 来控制打印的进度。使用两个 channel ,来分别控制数字和字母的打印序列, 数字打印完成后通过 channel 通知字母打印, 字母打印完成后通知数字打印,然后周而复始的工作。
源码参考
package main
import (
"fmt"
"sync"
)
func main() {
letter, number := make(chan bool), make(chan bool)
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
i := 1
for {
next := <-number
for j := 0; j < 2; j++ {
fmt.Print(i)
i++
}
if next {
letter <- true
continue
}
break
}
close(letter)
}()
go func() {
defer wg.Done()
l := 'A'
for l <= 'Z' {
<-letter
for i := 0; i < 2 && l <= 'Z'; i++ {
fmt.Print(string(l))
l++
}
number <- l <= 'Z'
}
close(number)
}()
number <- true
wg.Wait()
}
源码解析
- 使用
letter和number两个chan控制goroutine执行顺序,letter控制打印字母,number控制打印数字, 倒数第三行通知优先打印数字。 letter停止打印是字母表控制的, 当打印完Z字母之后即终止,number需要接受是否终止(退出)的事件,这个事件是由打印字母的goroutine控制的, 所以使用chan bool类型 , 为了保持一致性letter并没有采用chan struct{}类型,而是和number一致的chan bool。- 字母采用字节码计算, 可以很好的完成自增和边界检查。
- 当字母打印完
Z之母之后,会向number发送false, 用来表示letter不在接收新的任务,并且打印任务也将结束, 并且关闭number通道。 number接收到打印任务事件后,先打印数字,之后检查接收的next是否为true, 如果是true则通知letter进行字母打印, 否者的话就退出循环,并且关闭letter通道。- 两个
goroutine退出,wg.Wait() 结束,退出main函数,整个程序结束。
重点
- 程序中使用的
chan都是在写端关闭的,这是chan安全使用的最基本准则 - 程序退出前正确的关闭所有的
chan和goroutine相关资源