ch9/ch9-05 #205
Replies: 3 comments 1 reply
-
// pc[i] is the population count of i.
var pc [256]byte
func popCountInit() {
for i := range pc {
pc[i] = pc[i/2] + byte(i&1)
}
}
// PopCount returns the population count (number of set bits) of x.
func PopCount(x uint64) int {
initOnce.Do(popCountInit)
return int(pc[byte(x>>(0*8))] +
pc[byte(x>>(1*8))] +
pc[byte(x>>(2*8))] +
pc[byte(x>>(3*8))] +
pc[byte(x>>(4*8))] +
pc[byte(x>>(5*8))] +
pc[byte(x>>(6*8))] +
pc[byte(x>>(7*8))])
} |
Beta Was this translation helpful? Give feedback.
-
sync.Once惰性初始化假设你有一个应用需要处理大量的用户数据,这些数据需要从远程数据库中获取。如果你在应用启动时就加载所有的用户数据,那么这个初始化过程可能会花费很长的时间,并且消耗大量的内存。而且,如果你的应用只需要处理一部分用户的请求,那么在启动时加载所有用户数据实际上是不必要的。在这种情况下,更好的做法是在需要处理特定用户数据时再去加载这部分数据,这就是所谓的"延迟初始化"。 懒汉模式就是一个典型的例子。 但是就跟单例模式实现里存在的互斥问题一样,由于懒汉加载过程中并不是完全的原子操作,在并发上很可能就被编译器打乱了并发的顺序(不是说单个例程/线程内部的执行顺序)导致某个例程虽然一开始走在前面,但是还没来得及走完所有步骤,就被调度器调度(甚至可能一直处于饥饿),另一个例程后来居上,最后造成原子性被破坏,不同的例程读/写不同的值。 所以,一般我们就需要针对临界区做互斥的处理。 于是有了这段加锁的代码: mu.Lock()
defer mu.Unlock()
if icons == nil {
loadIcons()
}
return icons[name] 也可以再优化一下,加入读写锁,把仅读区加读锁,把写的临界区加写锁,提高一下性能。不过还是有点复杂,要加两次锁,解好几次锁(不同判断条件内解锁)。 于是引入 var loadIconsOnce sync.Once
var icons map[string]image.Image
// Concurrency-safe.
func Icon(name string) image.Image {
loadIconsOnce.Do(loadIcons)
return icons[name]
} 其内部实现是这样的: // Once是只执行一个动作的对象。
// A第一次使用后不得复制一次。
// 在Go内存模型的术语中,f的返回在任何一次调用once.Do(f)的返回之前“同步”。
type Once struct {
// done indicates whether the action has been performed.
// It is first in the struct because it is used in the hot path.
// The hot path is inlined at every call site.
// Placing done first allows more compact instructions on some architectures (amd64/386),
// and fewer instructions (to calculate offset) on other architectures.
done atomic.Uint32
m Mutex
}
/**
Do仅在Once的这个实例首次被调用时才调用函数f。换句话说,假设有一个变量once Once,如果多次调用once.Do(f),只有第一次调用会触发f,即使每次调用f的值都不同。每个要执行的函数都需要一个新的Once实例。
Do旨在用于必须恰好运行一次的初始化。由于f是无参数的,可能需要使用函数字面量来捕获要由Do调用的函数的参数:
config.once.Do(func() { config.init(filename) })
由于Do在f返回之前不会返回任何调用,如果f导致Do被调用,将会导致死锁。
如果f panics(即出现运行时错误),Do会认为它已经返回;未来的Do调用将不会调用f。
*/
func (o *Once) Do(f func()) {
if o.done.Load() == 0 {
// Outlined slow-path to allow inlining of the fast-path.
o.doSlow(f)
}
} 在 func (o *Once) doSlow(f func()) {
o.m.Lock()
defer o.m.Unlock()
if o.done.Load() == 0 {
defer o.done.Store(1)
f()
}
} 我们可以看到, 在 此外,Go源码中专门指出,直接使用 在Go语言中, |
Beta Was this translation helpful? Give feedback.
-
ch9/ch9-05
中文版
https://gopl-zh.github.io/ch9/ch9-05.html
Beta Was this translation helpful? Give feedback.
All reactions