1. sync.Once介绍
Go语言中的sync包中提供了一个针对只执行一次场景的解决方案–sync.Once。
1.1. 函数格式
sync.Once只有一个Do方法,其格式如下:
1 2 3 4 5 6 7 8 9 10 11 |
func (o *Once) Do(f func()) {} 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 uint32 m Mutex } |
1.2. 示例及解释
这里通过定义变量var once sync.Once
让其在多个协程中执行Do函数,最终能够保证协程安全,仅被执行一次. 具体实现原理可以参考文章Once原理.
once.Do 中的函数只会执行一次,并保证 once.Do 返回时,传入Do的函数已经执行完成。(多个 goroutine 同时执行 once.Do 的时候,可以保证抢占到 once.Do 执行权的 goroutine 执行完 once.Do 后,其他 goroutine 才能得到返回 )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
package main import ( "fmt" "sync" "time" ) func main() { var wg sync.WaitGroup //保证等待多个协程全部执行完再执行后续 var once sync.Once //保证仅被执行一次 onceBody := func() { time.Sleep(3e9) fmt.Println("Only once") } for i := 0; i < 10; i++ { wg.Add(1) go func(int) { defer wg.Done() once.Do(onceBody) fmt.Println(j) }(i) } wg.Wait() time.Sleep(3e9) } |
1.3. sync.Once封装结构
通常情况下,我们需要在once.Do()中的函数能够传递或返回某些变量,可以将其闭包使用;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
//once.go package once import "sync" type ReturnError struct { once sync.Once err error } func (owe *ReturnError) DoOnce(f func() error) error { owe.once.Do(func() { owe.err = f() }) return owe.err } //示例测试函数,使用参考如下once_test.go package once import ( "errors" "testing" ) const ( testErrorString = "This is a test error" ) func funcReturnErr() error { err := errors.New(testErrorString) return err } func TestMain(t *testing.T) { owe := &ReturnError{} //借用sync.once能保证 funcReturnErr只被执行一次 err := owe.DoOnce(funcReturnErr) if err != nil { t.Errorf("%v", err) } } |
2. sync.Map
2.1. 示例及解释
Go语言中内置的map不是并发安全的。请看下面的示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
var m = make(map[string]int) func get(key string) int { return m[key] } func set(key string, value int) { m[key] = value } func main() { wg := sync.WaitGroup{} for i := 0; i < 20; i++ { wg.Add(1) go func(n int) { key := strconv.Itoa(n) set(key, n) fmt.Printf("k=:%v,v:=%v\n", key, get(key)) wg.Done() }(i) } wg.Wait() } |
上面的代码开启少量几个goroutine的时候可能没什么问题,当并发多了之后执行上面的代码就会报fatal error: concurrent map writes错误。
像这种场景下就需要为map加锁来保证并发的安全性了,Go语言的sync包中提供了一个开箱即用的并发安全版map–sync.Map。开箱即用表示不用像内置的map一样使用make函数初始化就能直接使用。同时sync.Map内置了诸如Store、Load、LoadOrStore、Delete、Range等操作方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
var m = sync.Map{} func main() { wg := sync.WaitGroup{} for i := 0; i < 20; i++ { wg.Add(1) go func(n int) { key := strconv.Itoa(n) m.Store(key, n) value, _ := m.Load(key) fmt.Printf("k=:%v,v:=%v\n", key, value) wg.Done() }(i) } wg.Wait() } |
2.2. 函数格式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
// Load returns the value stored in the map for a key, or nil if no // value is present. // The ok result indicates whether value was found in the map. func (m *Map) Load(key interface{}) (value interface{}, ok bool) { // Store sets the value for a key. func (m *Map) Store(key, value interface{}) {} // LoadOrStore returns the existing value for the key if present. // Otherwise, it stores and returns the given value. // The loaded result is true if the value was loaded, false if stored. func (m *Map) LoadOrStore(key, value interface{}) (actual interface{}, loaded bool) // LoadAndDelete deletes the value for a key, returning the previous value if any. // The loaded result reports whether the key was present. func (m *Map) LoadAndDelete(key interface{}) (value interface{}, loaded bool) {} // Delete deletes the value for a key. func (m *Map) Delete(key interface{}){} // Range calls f sequentially for each key and value present in the map. // If f returns false, range stops the iteration. // // Range does not necessarily correspond to any consistent snapshot of the Map's // contents: no key will be visited more than once, but if the value for any key // is stored or deleted concurrently, Range may reflect any mapping for that key // from any point during the Range call. // // Range may be O(N) with the number of elements in the map even if f returns // false after a constant number of calls. func (m *Map) Range(f func(key, value interface{}) bool) {} |
3. 原子操作atomic
代码中的加锁操作因为涉及内核态的上下文切换会比较耗时、代价比较高。针对基本数据类型我们还可以使用原子操作来保证并发安全,因为原子操作是Go语言提供的方法它在用户态就可以完成,因此性能比加锁操作更好。Go语言中原子操作由内置的标准库sync/atomic提供。
赞赏微信赞赏
支付宝赞赏