哪些网站自己做宣传,一份简短的项目计划书,企业网站建设的上市公司,IT周末做网站违反制度么在使用Golang做后端开发的工程中#xff0c;我们通常需要声明一些一些配置类或服务单例等在业务逻辑层面较为底层的实例。为了节省内存或是冷启动开销#xff0c;我们通常采用lazy-load懒加载的方式去初始化这些实例。初始化单例这个行为是一个非常经典的并发处理的案例…在使用Golang做后端开发的工程中我们通常需要声明一些一些配置类或服务单例等在业务逻辑层面较为底层的实例。为了节省内存或是冷启动开销我们通常采用lazy-load懒加载的方式去初始化这些实例。初始化单例这个行为是一个非常经典的并发处理的案例比如在java当中我们可能用到建立双重锁volatile的方式保证初始化逻辑只被访问一次并且所有线程最终都可以读取到初始化完成的实例产物。这段经典的代码可以按如下的方式编写
// 参考https://blog.csdn.net/qq_27489007/article/details/84966680public class Singleton {private volatile static Singleton uniqueSingleton;private Singleton() {}public Singleton getInstance() {if (null uniqueSingleton) {synchronized (Singleton.class) {if (null uniqueSingleton) {uniqueSingleton new Singleton();}}}return uniqueSingleton;}
}但在Golang里面实现懒加载的方式可以简单的多用内置的sync.Once就能满足。假设我们有一个user单例需要被1000个线程读取并打印就可以这样子写
type User struct {Name string json:nameAge int json:age
}var user *User
var userOnce sync.Oncefunc initUser() {user User{}cfgStr : {name:foobar,age:18}if err : json.Unmarshal([]byte(cfgStr), user); err ! nil {panic(load user err: err.Error())}
}func getUser() *User {userOnce.Do(initUser)return user
}func TestSyncOnce(t *testing.T) {var wg sync.WaitGroupfor i : 1; i 1000; i {wg.Add(1)go func(n int) {defer wg.Done()curUser : getUser()t.Logf([%d] got user: %v, n, curUser)}(i)}wg.Wait()
}这段代码里首先是通过var userOnce sync.Once声明了一个sync.Once实例然后在getUser当中我们声明了userOnce.Do(initUser)这个操作。假设一个goroutine最先到达这个操作就会上锁并执行initUser其它goroutine到达之后得等第一个goroutine执行完initUser之后才会继续return user。这样就能一来保证initUser只会执行一次二来所有goroutine都能够最终读到初始化完成的user单例。
sync.Once的工作机理也很简单通过一个锁和一个flag就能够实现
func (o *Once) Do(f func()) {if atomic.LoadUint32(o.done) 0 { // 如果是1表示已经完成了跳过o.doSlow(f)}
}func (o *Once) doSlow(f func()) {o.m.Lock() // 只有1个goroutine能拿到锁其它的等待defer o.m.Unlock()if o.done 0 { // 如果还是0表示第一个来的不是0就表示已经有goroutine做完了defer atomic.StoreUint32(o.done, 1)f()}
}最后也需要注意sync.Once使用上面有一个坑点不能也不需要像java一样为单例提前做nil判断。比如下面一段代码是有问题的
func initUser() {user User{} // 先给一个zero-value实例cfgStr : {name:foobar,age:18} // 然后加载json内容完成初始化if err : json.Unmarshal([]byte(cfgStr), user); err ! nil {panic(load user err: err.Error())}
}func getUser() *User {if user nil {userOnce.Do(initUser)}return user
}由于Golang没有volatile关键字不能控制单例在内存的可见性那么多goroutine并发时就有可能出现这样的执行时序
goroutine-A过了getUser的user nil判断进入到了initUser逻辑走到了cfgStr : XXX一行此时切换到goroutine-B因为goroutine-A在initUser已经走过了user User{}一行所以跳过了user nil判断直接返回没有完全初始化的user实例然后一直往下运行就没切回给goroutine-A
这样的结果就导致有goroutine拿到未初始化完成的实例往后运行后面就出问题了。所以实战当中需要留意用sync.Once时不能也不需要加这些nil判断就能满足懒加载单例/配置之类的逻辑。