面试题答案
一键面试陷阱
- 重复初始化风险:虽然
sync.Once
设计用于只执行一次初始化,但如果在初始化函数内部调用可能会触发重新进入初始化逻辑的代码,可能导致看似重复初始化的问题。例如在初始化函数中调用了其他可能再次触发初始化逻辑的函数。 - 初始化函数内错误处理问题:如果初始化函数执行失败(例如数据库连接失败、文件读取失败等),
sync.Once
不会自动重试,后续使用可能因为初始化未成功而导致程序错误。并且没有直接的机制来检查初始化函数是否执行成功。 - 死锁风险:如果在初始化函数中使用锁(例如互斥锁),且初始化逻辑与其他使用共享资源的逻辑存在锁竞争,可能会导致死锁。例如初始化函数中获取一个锁,而其他并发操作也在获取该锁且顺序不当。
优化方式及示例
- 错误处理优化:将初始化逻辑封装在一个函数中,该函数返回错误。在
sync.Once
执行完初始化后检查错误。
package main
import (
"fmt"
"sync"
)
var (
once sync.Once
resource *string
err error
)
func initResource() error {
newResource := "initialized resource"
// 模拟可能失败的操作,例如数据库连接等
// if someCondition {
// return fmt.Errorf("initialization failed")
// }
resource = &newResource
return nil
}
func getResource() (*string, error) {
once.Do(func() {
err = initResource()
})
if err != nil {
return nil, err
}
return resource, nil
}
func main() {
result, err := getResource()
if err != nil {
fmt.Println("Error:", err)
} else {
fmt.Println("Resource:", *result)
}
}
- 避免死锁优化:确保初始化函数内的锁使用与其他并发操作的锁使用不会产生死锁。例如,尽量减少初始化函数内锁的使用,或者确保锁获取顺序的一致性。
package main
import (
"fmt"
"sync"
)
var (
once sync.Once
mu sync.Mutex
data []int
)
func initData() {
mu.Lock()
defer mu.Unlock()
// 初始化数据
data = []int{1, 2, 3}
}
func addToData(value int) {
once.Do(initData)
mu.Lock()
defer mu.Unlock()
data = append(data, value)
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func(v int) {
defer wg.Done()
addToData(v)
}(i)
}
wg.Wait()
mu.Lock()
fmt.Println("Data:", data)
mu.Unlock()
}
在这个示例中,通过合理安排锁的获取顺序,避免了可能的死锁。同时使用 sync.Once
确保 initData
只被调用一次。