面试题答案
一键面试1. 程序输出顺序及执行逻辑分析
- 主函数
main
开始执行:调用nestedFunction
。 - 进入
nestedFunction
:- 初始化
sync.WaitGroup
并添加计数为2,准备等待两个goroutine完成。 - 遇到外层
defer
,将其压入延迟调用栈。此时该defer
不会立即执行,而是在nestedFunction
函数结束时执行。其逻辑是打印"outer defer"
并等待两个goroutine完成(通过wg.Wait()
)。 - 启动第一个goroutine:
- 进入该goroutine,将第一个
defer
(打印"inner go1 defer"
)压入延迟调用栈。 - 再将内层的
defer
(打印"inner go1 sub - defer"
)压入延迟调用栈。 - 打印
"inner go1"
。 - 调用
wg.Done()
,减少WaitGroup
的计数。 - 该goroutine结束,按照延迟调用栈的顺序,先打印
"inner go1 sub - defer"
,再打印"inner go1 defer"
。
- 进入该goroutine,将第一个
- 启动第二个goroutine:
- 进入该goroutine,将第一个
defer
(打印"inner go2 defer"
)压入延迟调用栈。 - 再将内层的
defer
(打印"inner go2 sub - defer"
)压入延迟调用栈。 - 打印
"inner go2"
。 - 调用
wg.Done()
,减少WaitGroup
的计数。 - 该goroutine结束,按照延迟调用栈的顺序,先打印
"inner go2 sub - defer"
,再打印"inner go2 defer"
。
- 进入该goroutine,将第一个
nestedFunction
函数执行完毕,执行外层defer
,打印"outer defer"
并等待两个goroutine完成(此时两个goroutine已完成,wg.Wait()
立即返回)。
- 初始化
- 回到
main
函数:打印"main end"
。
所以,正常输出顺序为:
inner go1
inner go2
inner go1 sub - defer
inner go2 sub - defer
inner go1 defer
inner go2 defer
outer defer
main end
2. 在外层 defer
中添加 time.Sleep(time.Second)
后的输出顺序变化
- 主函数
main
开始执行:调用nestedFunction
。 - 进入
nestedFunction
:- 初始化
sync.WaitGroup
并添加计数为2,准备等待两个goroutine完成。 - 遇到外层
defer
,将其压入延迟调用栈。 - 启动第一个goroutine:
- 进入该goroutine,将第一个
defer
(打印"inner go1 defer"
)压入延迟调用栈。 - 再将内层的
defer
(打印"inner go1 sub - defer"
)压入延迟调用栈。 - 打印
"inner go1"
。 - 调用
wg.Done()
,减少WaitGroup
的计数。 - 该goroutine结束,按照延迟调用栈的顺序,先打印
"inner go1 sub - defer"
,再打印"inner go1 defer"
。
- 进入该goroutine,将第一个
- 启动第二个goroutine:
- 进入该goroutine,将第一个
defer
(打印"inner go2 defer"
)压入延迟调用栈。 - 再将内层的
defer
(打印"inner go2 sub - defer"
)压入延迟调用栈。 - 打印
"inner go2"
。 - 调用
wg.Done()
,减少WaitGroup
的计数。 - 该goroutine结束,按照延迟调用栈的顺序,先打印
"inner go2 sub - defer"
,再打印"inner go2 defer"
。
- 进入该goroutine,将第一个
nestedFunction
函数执行完毕,执行外层defer
,先睡眠1秒,然后打印"outer defer"
并等待两个goroutine完成(此时两个goroutine已完成,wg.Wait()
立即返回)。
- 初始化
- 回到
main
函数:打印"main end"
。
输出顺序变为:
inner go1
inner go2
inner go1 sub - defer
inner go2 sub - defer
inner go1 defer
inner go2 defer
// 等待1秒
outer defer
main end
原因是 time.Sleep(time.Second)
会阻塞当前goroutine(这里是执行外层 defer
的goroutine)1秒钟,所以在两个goroutine完成后会等待1秒再打印 "outer defer"
。
3. 修改代码使 inner go1 sub - defer
和 inner go2 sub - defer
在 outer defer
之前执行
可以通过使用通道(channel)来同步goroutine。修改后的代码如下:
package main
import (
"fmt"
"sync"
"time"
)
func nestedFunction() {
var wg sync.WaitGroup
wg.Add(2)
var subDeferChan = make(chan struct{})
go func() {
defer fmt.Println("inner go1 defer")
defer func() {
fmt.Println("inner go1 sub - defer")
subDeferChan <- struct{}{}
}()
fmt.Println("inner go1")
wg.Done()
}()
go func() {
defer fmt.Println("inner go2 defer")
defer func() {
fmt.Println("inner go2 sub - defer")
subDeferChan <- struct{}{}
}()
fmt.Println("inner go2")
wg.Done()
}()
go func() {
wg.Wait()
close(subDeferChan)
}()
defer func() {
fmt.Println("outer defer")
}()
for i := 0; i < 2; i++ {
<-subDeferChan
}
}
func main() {
nestedFunction()
fmt.Println("main end")
}
- 添加通道
subDeferChan
:用于同步内层子defer
的执行。 - 在每个内层子
defer
中:向通道发送信号。 - 添加一个新的goroutine:等待两个goroutine完成(通过
wg.Wait()
)后关闭通道。 - 在外层
defer
之前:通过循环从通道接收两个信号,确保内层子defer
都执行完毕。这样就可以保证inner go1 sub - defer
和inner go2 sub - defer
在outer defer
之前执行,同时保持整体逻辑的正确性。