Go并发:利用sync.Once延迟加载

总所周知,延迟加载会提高程序的加载速度,有时还能够节省内存。比如如下程序:

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程序设计语言》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值