前提
这应该是一道经典的golang的channel的面试题了,这段时间在准备面试,就简单些一下来实现一个简单高效的channel混合打印的方法首先看题:
有三个函数分别打印,“dog”,“cat”,“fish”, 要求每个函数起一个goroutine,请按照dog,cat,fish的顺序,打印一百次,输出到控制台。
分析
从题目上看一下需要每个函数都需要启动一个goroutine。众所周知go协程是无序的,所以如果想要按照顺序打印对应字符串就需要通过一些方法来控制协程的执行顺序,这时候就需要用到golang里面协程最重要的工具channel
func main() {
c1, c2, c3 := make(chan struct{}, 1), make(chan struct{}, 1), make(chan struct{}, 1)
go func() {
for i := 0; i < 100; i++ {
<-c1
fmt.Println("dog")
c2 <- struct{}{}
}
}()
go func() {
for i := 0; i < 100; i++ {
<-c2
fmt.Println("cat")
c3 <- struct{}{}
}
}()
go func() {
for i := 0; i < 100; i++ {
<-c3
fmt.Println("fish")
c1 <- struct{}{}
}
}()
c1 <- struct{}{}
select {}
}
这是我写出来的第一版的代码,虽然是顺序打印并且打印了一百次,运行会报错 报错如下图。
死锁了,这是因为我们不知道合适该停止这些协程,并互相等待导致死锁。
改进
func main() {
wg := sync.WaitGroup{}
wg.Add(3)
c1, c2, c3 := make(chan struct{}, 1), make(chan struct{}, 1), make(chan struct{}, 1)
go func() {
defer wg.Done()
for i := 0; i < 100; i++ {
<-c1
fmt.Println("dog")
c2 <- struct{}{}
}
}()
go func() {
defer wg.Done()
for i := 0; i < 100; i++ {
<-c2
fmt.Println("cat")
c3 <- struct{}{}
}
}()
go func() {
defer wg.Done()
for i := 0; i < 100; i++ {
<-c3
fmt.Println("fish")
c1 <- struct{}{}
}
}()
c1 <- struct{}{}
wg.Wait()
}
通过添WaitGroup就可以在循环执行完毕后直接执行wg.Done() 当所有的协程都执行完毕后 wg.Wait()就会执行。
改进 2.0
上面那些代码还是有很多重复的代码可以抽离。接下来我们进行代码的简单抽离,提高代码可读性。
func main() {
wg := sync.WaitGroup{}
c1, c2, c3 := make(chan struct{}, 1), make(chan struct{}, 1), make(chan struct{}, 1)
wg.Add(3)
go animalsLog(c1, c2, &wg, "cat")
go animalsLog(c2, c3, &wg, "dog")
go animalsLog(c3, c1, &wg, "fish")
c1 <- struct{}{}
wg.Wait()
}
func animalsLog(c1, c2 chan struct{}, wg *sync.WaitGroup, animals string) {
defer wg.Done()
for i := 0; i < 100; i++ {
<-c1
fmt.Println(animals)
c2 <- struct{}{}
}
}
我们抽离出来一个animalsLog方法,用来打印指定动物名称,这样就完成了我们最终的代码。