-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Sync.Once 实现单例与 sync.Pool #5
Comments
深入理解 sync.Once 与 sync.Pool
var once sync.Once
for i:=0; i < 10; i++ {
once.Do(func(){
fmt.Println("execed...")
})
} 在上面的例子中,once.Do 的参数 func 函数就会保证只执行一次。 sync.Once 原理那么 sync.Once 是如何保证 Do 执行体函数只执行一次呢? 从 sync.Once 的源码就可以看出其实就是通过一个 uint32 类型的 done 标识实现的。当 package sync
import (
"sync/atomic"
)
type Once struct {
done uint32
m Mutex
}
func (o *Once) Do(f func()) {
if atomic.LoadUint32(&o.done) == 0 {
o.doSlow(f)
}
}
func (o *Once) doSlow(f func()) {
o.m.Lock()
defer o.m.Unlock()
if o.done == 0 {
defer atomic.StoreUint32(&o.done, 1)
f()
}
}
如果仔细查看源代码中的注释就会发现 go 团队还解释了为什么没有使用 cas 这种同步原语实现。因为 func (o *Once) Do(f func()) {
if atomic.CompareAndSwapUint32(&o.done, 0, 1) {
f()
}
} 虽然 cas 保证了同一时刻只有一个请求进入 if 判断执行 f()。但是其它的请求却没有等待 f() 执行完成就立即返回了。那么用户端在执行 once.Do 返回之后其实就可能存在 f() 还未完成,就会出现意料之外的错误。如下面例子 var db SqlDb
var once sync.Once
for i:=0; i < 2; i++ {
once.Do(func() {
db = NewSqlDB()
fmt.Println("execed...")
})
}
// #1
db.Query("select * from table")
... 根据上述如果是用 cas 实现的 once,那么当 sync.Once 使用限制由于 Go 语言一切皆 struct 的特性,我们在使用 sync.Once 的时候一定要注意不要通过传递参数使用。因为 go 对于 sync.Once 参数传递是值传递,会将原来的 once 拷贝过来,所以有可能会导致 once 会重复执行或者是已经执行过了就不会执行的问题。 func main() {
for i := 0; i < 10; i++ {
once.Do(func() {
fmt.Println("execed...")
})
}
duplicate(once)
}
func duplicate(once sync.Once) {
for i := 0; i < 10; i++ {
once.Do(func() {
fmt.Println("execed2...")
})
}
} 比如上述例子,由于 once 已经执行过一次,once.done 已经为 1。这个时候再通过传递,由于 once.done 已经为1,所以就不会执行了。上面的输出结果只会打印第一段循环的结果 sync.Poolsync.Pool 其实把初始化的对象放到内部的一个池对象中,等下次访问就直接返回池中的对象,如果没有的话就会生成这个对象放入池中。Pool 的目的是”预热“,即初始化但还未立即使用的对象,由于预先初始化至 Pool,所以到后续取得时候就直接返回已经初始化过得对象即可。这样提高了程序吞吐,因为有时候在运行时初始化一些对象的开销是非常昂贵的,如数据库连接对象等。 现在我们来深入分析 Pool sync.Pool 原理sync.Pool 核心对象有三个
New funcPool 的结构很简单,就 5 个字段 type Pool struct {
...
New func() interface{}
} 字段 poolLocalInternal在将 Get、Put 之前得先了解 poolLocalInternal 这个对象,里面只有两个对象,都是用来存储要用的对象的: type poolLocalInternal struct {
private interface{} // Can be used only by the respective P.
shared poolChain // Local P can pushHead/popHead; any P can popTail.
} 操作这个对象时必须要把当前的 goroutine 绑定到 P,并且禁止让出 g。在 Get 和 Put 操作时都是优先操作 Get每个当前 goroutine 都拥有一个
从上面的调用过程来看,Pool.Get 获取值的过程在一定程度与 gmp 模型有很多相似的地方的。 PutPut 操作就比较简单了,优先将值赋值给 sync.Pool 使用限制因为 pool 每次的 get 操作都会将值 |
No description provided.
The text was updated successfully, but these errors were encountered: