第一次用 Golang 出面试题小记

最近一直在学习 Golang ,正好今天遇到一个七牛的候选人,就顺带出了一个多线程的题目让他用 Golang 写,在他写的过程中我担心自己没法给他 review code (我也是个新手),就在自己的电脑上尝试了一把,rush 的比较快,但是还是遇到了 docklock的问题,在这里记录一下

考题

用两个goroutine分别打印奇数和偶数的方式,输出1~10

实现思路

这个就是考核一下线程切换的,在java基本就是靠object.waitobject.notifyAll来实现,不够用Golang的channel 感受上会不太一样

这个题目的关键点有3个

  1. 确保奇数的goroutine是一定在偶数之前运行的
  2. 奇数和偶数的交替输出
  3. 程序正常退出

想法上就是弄4个channel

  1. startChan 负责保证奇数一定先开始
  2. notifyEvennotifyOdd 用来做线程切换
  3. done 用来做任务退出
1
2
3
4
startChan := make(chan int)
notifyEven := make(chan int, 1)
notifyOdd := make(chan int, 1)
done := make(chan int, 2)

buffer 的设计主要是done, 因为有2个goroutine, 防止在写入的时候block,给了2的buffer


Goroutine 代码

####奇数代码

1
2
3
4
5
6
7
8
9
10
11
go func() {
// pending on startChan
<-startChan
for i := 1; i < 10; i += 2 {
fmt.Println(i)
notifyEven <- 1
<-notifyOdd
}

done <- 1
}()

####偶数代码

1
2
3
4
5
6
7
8
9
10
go func() {
// pending on startChan
<-notifyEven
for i := 2; i <= 10; i += 2 {
fmt.Println(i)
notifyOdd <- 1
<-notifyEven
}
done <- 1
}()

####主函数代码

1
2
3
4
5
startChan <- 1

for i:=0; i<2; i++ {
<-done
}

解释一下

  1. 奇数线程pending 在<-startChan上,等主线程startChan <- 1来唤醒
  2. 奇数偶数之间互现唤醒和沉睡,做完全部工作后,调用done <- 1
  3. 主线程 block 在 <-done上,等所有工作线程做完了,主线程组再推出

一切看似很快就写完了,但是一运行,还是遇到了deadlock问题


问题排查和改进

deadlock bug fix

怎么会deadlock呢,想了一下,哦,原来奇数再打印了9以后就推出了,但是这个时候偶数还block在<-notifyEven上,所以就死锁了饿,Fix 非常简单,退出的时候通过notifyEven <- 1把偶数唤醒就好了

sync.WaitGroup

done 这个事情看起来就是以前 java 里面的join在做的事情,这种common的事情还要自己写,太low了,查了一下原来golang是有一个sync库做类似的事情,用waitGroup替换done就好了,最后贴一下现在的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
 startChan := make(chan int)
notifyEven := make(chan int, 1)
notifyOdd := make(chan int, 1)

var wg sync.WaitGroup
//done := make(chan int, 2)
wg.Add(2)

go func() {
// pending on startChan
<-startChan
for i := 1; i < 10; i += 2 {
fmt.Println(i)
notifyEven <- 1
<-notifyOdd
}
//
notifyEven <- 1
//done <- 1
wg.Done()
}()

go func() {
// pending on startChan
<-notifyEven
for i := 2; i <= 10; i += 2 {
fmt.Println(i)
notifyOdd <- 1
<-notifyEven
}
//done <- 1
wg.Done()
}()

startChan <- 1

wg.Wait()
fmt.Println("done test")

此刻我想说一句,go写并发真有趣