Skip to content

Commit efa13c9

Browse files
committed
扩展sync.Once
1 parent 1a40af1 commit efa13c9

File tree

3 files changed

+239
-0
lines changed

3 files changed

+239
-0
lines changed

exsync/doc.go

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// MIT License
2+
// Copyright (c) 2020 Qi Yin <[email protected]>
3+
4+
// Package exsync 对 sync 的扩展
5+
package exsync

exsync/once.go

+96
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
// MIT License
2+
// Copyright (c) 2020 Qi Yin <[email protected]>
3+
4+
package exsync
5+
6+
import (
7+
"sync"
8+
"unsafe"
9+
)
10+
11+
// Once 是 sync.Once 的扩展实现,由于每次使用 sync.Once 都需要保存两个字段,一个是 sync.Once 的实例,一个是数据本身,这带来一些混乱
12+
// 让 Once 自带数据保存,减少使用时需要定义多个字段,如果需要保存多个数据,可以使用 []interface{} 或者自定义 struct
13+
//
14+
// 以下是一个简单的示例:
15+
// var db Once
16+
// func DB() *mysql.Client{
17+
// return db.Do(f func() interface{}{
18+
// return mysql.NewClient(...)
19+
// }).(*mysql.Client)
20+
// }
21+
//
22+
// 当希望处理错误,可以响应 []interface{} 或者 自定义 struct, 如果在服务或程序初始化阶段可以考虑 panic 来报告错误,如下是使用 []interface{} 的示例:
23+
// var db Once
24+
// func DB() (*mysql.Client, error){
25+
// res := db.Do(f func() interface{}{
26+
// c, err:=mysql.NewClient(...)
27+
// return []interface{}{c, err}
28+
// }).([]interface{})
29+
//
30+
// return res[0].(*mysql.Client), res[1].(error)
31+
// }
32+
//
33+
// 使用该方法需要一些取舍,它简单实用,相比 sync.Once 性能有所下降,不过它依然很快,这不会形成性能问题。
34+
type Once struct {
35+
once sync.Once
36+
v interface{}
37+
}
38+
39+
// Do calls the function f if and only if Do is being called for the
40+
// first time for this instance of Once. In other words, given
41+
// var once Once
42+
// if once.Do(f) is called multiple times, only the first call will invoke f,
43+
// even if f has a different value in each invocation. A new instance of
44+
// Once is required for each function to execute.
45+
//
46+
// Do is intended for initialization that must be run exactly once. Since f
47+
// is niladic, it may be necessary to use a function literal to capture the
48+
// arguments to a function to be invoked by Do:
49+
// config.once.Do(func() { config.init(filename) })
50+
//
51+
// Because no call to Do returns until the one call to f returns, if f causes
52+
// Do to be called, it will deadlock.
53+
//
54+
// If f panics, Do considers it to have returned; future calls of Do return
55+
// without calling f.
56+
//
57+
func (o *Once) Do(f func() interface{}) interface{} {
58+
o.once.Do(func() {
59+
o.v = f()
60+
})
61+
62+
return o.v
63+
}
64+
65+
// OncePointer 性能方面略好于 Once,单不会有太大改善,依然落后于 sync.Once, 在某些场景下可以使用,更推荐使用 Once
66+
type OncePointer struct {
67+
once sync.Once
68+
v unsafe.Pointer
69+
}
70+
71+
// Do calls the function f if and only if Do is being called for the
72+
// first time for this instance of Once. In other words, given
73+
// var once Once
74+
// if once.Do(f) is called multiple times, only the first call will invoke f,
75+
// even if f has a different value in each invocation. A new instance of
76+
// Once is required for each function to execute.
77+
//
78+
// Do is intended for initialization that must be run exactly once. Since f
79+
// is niladic, it may be necessary to use a function literal to capture the
80+
// arguments to a function to be invoked by Do:
81+
// config.once.Do(func() { config.init(filename) })
82+
//
83+
// Because no call to Do returns until the one call to f returns, if f causes
84+
// Do to be called, it will deadlock.
85+
//
86+
// If f panics, Do considers it to have returned; future calls of Do return
87+
// without calling f.
88+
//
89+
func (o *OncePointer) Do(f func() unsafe.Pointer) unsafe.Pointer {
90+
o.once.Do(func() {
91+
o.v = f()
92+
})
93+
94+
return o.v
95+
}
96+

exsync/once_test.go

+138
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
// MIT License
2+
// Copyright (c) 2020 Qi Yin <[email protected]>
3+
4+
package exsync
5+
6+
import (
7+
"testing"
8+
"unsafe"
9+
)
10+
11+
type one int
12+
13+
func (o *one) Increment() {
14+
*o++
15+
}
16+
17+
func run(t *testing.T, once *Once, o *one, c chan bool) {
18+
v:=once.Do(func() interface{} {
19+
o.Increment()
20+
return o
21+
}).(*one)
22+
23+
if *v != 1 {
24+
t.Errorf("once failed inside run: %d is not 1", v)
25+
}
26+
c <- true
27+
}
28+
29+
func TestOnce(t *testing.T) {
30+
o := new(one)
31+
once := new(Once)
32+
c := make(chan bool)
33+
const N = 10
34+
for i := 0; i < N; i++ {
35+
go run(t, once, o, c)
36+
}
37+
for i := 0; i < N; i++ {
38+
<-c
39+
}
40+
if *o != 1 {
41+
t.Errorf("once failed outside run: %d is not 1", *o)
42+
}
43+
}
44+
45+
func TestOncePanic(t *testing.T) {
46+
var once Once
47+
func() {
48+
defer func() {
49+
if r := recover(); r == nil {
50+
t.Fatalf("Once.Do did not panic")
51+
}
52+
}()
53+
54+
_ = once.Do(func() interface{}{
55+
panic("failed")
56+
return nil
57+
}).(*one)
58+
}()
59+
60+
_ = once.Do(func() interface{}{
61+
t.Fatalf("Once.Do called twice")
62+
return nil
63+
})
64+
}
65+
66+
func BenchmarkOnce(b *testing.B) {
67+
var once Once
68+
var o = new(one)
69+
f := func() interface{}{return o}
70+
b.RunParallel(func(pb *testing.PB) {
71+
for pb.Next() {
72+
_ = once.Do(f).(*one)
73+
}
74+
})
75+
}
76+
77+
78+
func runPointer(t *testing.T, once *OncePointer, o *one, c chan bool) {
79+
v:=once.Do(func() unsafe.Pointer {
80+
o.Increment()
81+
return unsafe.Pointer(o)
82+
})
83+
84+
if *(*int)(v) != 1 {
85+
t.Errorf("once failed inside run: %d is not 1", *(*int)(v))
86+
}
87+
c <- true
88+
}
89+
90+
func TestOncePointer(t *testing.T) {
91+
o := new(one)
92+
once := new(OncePointer)
93+
c := make(chan bool)
94+
const N = 10
95+
for i := 0; i < N; i++ {
96+
go runPointer(t, once, o, c)
97+
}
98+
for i := 0; i < N; i++ {
99+
<-c
100+
}
101+
if *o != 1 {
102+
t.Errorf("once failed outside run: %d is not 1", *o)
103+
}
104+
}
105+
106+
func TestOncePointerPanic(t *testing.T) {
107+
var once OncePointer
108+
func() {
109+
defer func() {
110+
if r := recover(); r == nil {
111+
t.Fatalf("Once.Do did not panic")
112+
}
113+
}()
114+
115+
once.Do(func() unsafe.Pointer{
116+
panic("failed")
117+
return nil
118+
})
119+
}()
120+
121+
_ = once.Do(func() unsafe.Pointer{
122+
t.Fatalf("Once.Do called twice")
123+
return nil
124+
})
125+
}
126+
127+
func BenchmarkOncePointer(b *testing.B) {
128+
var once OncePointer
129+
var o = new(one)
130+
f := func() unsafe.Pointer{
131+
return unsafe.Pointer(o)
132+
}
133+
b.RunParallel(func(pb *testing.PB) {
134+
for pb.Next() {
135+
_ = (*one)(once.Do(f))
136+
}
137+
})
138+
}

0 commit comments

Comments
 (0)