面试题答案
一键面试面临的挑战
- 资源竞争:多个goroutine同时通过defer清理同一资源,可能导致资源竞争问题,例如文件描述符的重复关闭,数据库连接的重复释放等,这会引发程序崩溃或数据不一致。
- 死锁风险:如果在defer清理函数中存在同步操作(如获取锁),并且不同goroutine的defer清理顺序不当,可能会导致死锁。例如,goroutine A在defer中获取锁L1,然后等待锁L2,而goroutine B在defer中获取锁L2,然后等待锁L1,就会形成死锁。
解决方案
- 使用互斥锁(Mutex)
- 原理:通过互斥锁保证同一时间只有一个goroutine可以访问和清理资源,避免资源竞争。
- 代码示例:
package main
import (
"fmt"
"sync"
)
var (
resource = 0
mu sync.Mutex
)
func worker(wg *sync.WaitGroup) {
defer wg.Done()
mu.Lock()
defer mu.Unlock()
// 使用资源
resource++
fmt.Println("Using resource, current value:", resource)
// 模拟业务逻辑
// 清理资源
fmt.Println("Cleaning up resource")
resource--
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go worker(&wg)
}
wg.Wait()
fmt.Println("Final resource value:", resource)
}
- **解释**:在`worker`函数中,通过`mu.Lock()`和`mu.Unlock()`(使用defer确保解锁)保证了同一时间只有一个goroutine可以访问和修改`resource`,避免了资源竞争。
2. 使用sync.Once
- 原理:sync.Once
类型的变量可以保证其函数只被执行一次,无论有多少个goroutine试图调用。这可以用于确保资源只被清理一次。
- 代码示例:
package main
import (
"fmt"
"sync"
)
var (
resource = 0
once sync.Once
)
func worker(wg *sync.WaitGroup) {
defer wg.Done()
// 使用资源
resource++
fmt.Println("Using resource, current value:", resource)
// 模拟业务逻辑
// 清理资源
once.Do(func() {
fmt.Println("Cleaning up resource")
resource--
})
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go worker(&wg)
}
wg.Wait()
fmt.Println("Final resource value:", resource)
}
- **解释**:在`worker`函数中,`once.Do`确保了资源清理函数只被执行一次,即使多个goroutine都调用了`once.Do`,这样就避免了重复清理资源的问题。