总所周知,延迟加载会提高程序的加载速度,有时还能够节省内存。比如如下程序:
package main
import (
"image"
)
var icons map[string]image.Image // 保存所有的图标
func Icon(name string) image.Image {
if icons == nil { // 延迟加载
loadIcons()
}
return icons[name]
}
// 加载所有图标
func loadIcons() {
icons = make(map[string]image.Image)
icons["warning.png"] = loadIcon("warning.png")
icons["header.png"] = loadIcon("header.png")
icons["footer.png"] = loadIcon("footer.png")
}
// 加载图标
func loadIcon(fname string) image.Image {
var icon image.Image
// ...
return icon
}
func main() {
go func() { // 协程A
icon := Icon("header.png")
//...
}()
go func() { // 协程B
icon := Icon("footer.png")
// ...
}()
// ...
}
以上程序会出现竟险(Race Condition),因为在多个协程中调用了Icon函数,而在Icon函数内部则进行了资源的加载。有可能在协程A中判断if icons == nil
为true后,刚刚进入加载流程的时候(此时icons仍为nil),协程B也开始判断if icons == nil
(true)并且进入加载流程,导致icons的多次加载,有可能造成icons内部数据的破坏。我们可以加锁来避免这样的问题:
var (
icons map[string]image.Image
mu sync.Mutex // 互斥对象
)
func Icon(name string) image.Image {
// 使用互斥锁对共享资源进行保护
mu.Lock()
defer mu.Unlock()
if icons == nil {
loadIcons()
}
return icons[name]
}
不过还有更简单的处理方式,Go考虑到这种方式的通用性,专门提供了 sync.Once 来处理这种情况,sync.Once内部实现中包含了一个bool类型和一个mutex,其中bool表示初始化是否完成。sync.Once提供了一个协程安全的Do()方法(利用sync.Once内部的mutex),参数为一个函数对象。当调用Do方法的时候,会根据bool值判断,如果没有初始化,则调用传递给Do的函数对象以完成初始化,否则什么也不做:
var (
icons map[string]image.Image
loadIconOnce sync.Once
)
func Icon(name string) image.Image {
// 使用 sync.Once 仅加载一次资源
// 注意Do方法是协程安全的
loadIconOnce.Do(loadIcons)
return icons[name]
}
本文参考自《Go程序设计语言》