這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
go語言自身提供了一種不使用鎖來解決並發安全的行為那就是atomic.Value, 我們將指標指向golang 1.4 文檔。
1.4中 在 Minor changes to the library 標記中新加了atomic.Value 類型,可以原子的載入和儲存任意類型值,當我看到這個新提供的類型之後(1.4的時候)我很開心也很鬱悶,因為官方包上寫著
Package atomic provides low-level atomic memory primitives useful for implementing synchronization algorithms.
These functions require great care to be used correctly. Except for special, low-level applications, synchronization is better done with channels or the facilities of the sync package. Share memory by communicating; don't communicate by sharing memory.
所以我看完這句話也就繼續在項目中使用鎖和channel來處理並發安全,後來一個機會我看了下nsq的源碼,發現裡面大量的使用atomic.Value 於是在1.6之後我也開始在項目中大量的使用atomic.Value
package mainimport ("fmt""sync/atomic")type Value struct {Key stringVal interface{}}type Noaway struct {Movies atomic.ValueTotal atomic.Value}func NewNoaway() *Noaway {n := new(Noaway)n.Movies.Store(&Value{Key: "movie", Val: "Wolf Warrior 2"})n.Total.Store("$2,539,306")return n}func main() {n := NewNoaway()val := n.Movies.Load().(*Value)total := n.Total.Load().(string)fmt.Printf("Movies %v domestic total as of Aug. 27, 2017: %v \n", val.Val, total)}
這個例子看起來很好,安全執行緒還沒有鎖,邏輯複雜的時候也不用使用defer了,我使用benchmark和鎖對比一下看看是否是自己想要的結果:
package noaway_testimport ("sync""sync/atomic""testing")type manager struct {sync.RWMutexagents int}func BenchmarkManagerLock(b *testing.B) {m := new(manager)b.ReportAllocs()b.RunParallel(func(pb *testing.PB) {for pb.Next() {m.Lock()m.agents = 100m.Unlock()}})}func BenchmarkManagerRLock(b *testing.B) {m := manager{agents: 100}b.ReportAllocs()b.RunParallel(func(pb *testing.PB) {for pb.Next() {m.RLock()_ = m.agentsm.RUnlock()}})}func BenchmarkManagerAtomicValueStore(b *testing.B) {var managerVal atomic.Valuem := manager{agents: 100}b.ReportAllocs()b.RunParallel(func(pb *testing.PB) {for pb.Next() {managerVal.Store(m)}})}func BenchmarkManagerAtomicValueLoad(b *testing.B) {var managerVal atomic.ValuemanagerVal.Store(&manager{agents: 100})b.ReportAllocs()b.RunParallel(func(pb *testing.PB) {for pb.Next() {_ = managerVal.Load().(*manager)}})}
確實是自己想要的結果 ?,但是你在文檔中會看到這樣的注釋
func (*Value) Load
func (v *Value) Load() (x interface{})
Load returns the value set by the most recent Store. It returns nil if there has been no call to Store for this Value.
func (*Value) Store
func (v *Value) Store(x interface{})
Store sets the value of the Value to x. All calls to Store for a given Value must use values of the same concrete type. Store of an inconsistent type panics, as does Store(nil).
事實證明我們在使用atomic的時候還是遵從官方的使用方式,以下是一個老外遇到的情況:
I’ve received a private report about poor encoding/gob performance on a 80-core machine. encoding/gob has a bunch of global mutexes. By removing just one of them, I’ve got 2x speedup on 16-core machine. If all mutexes are removed on the 80-core machine it can easily make 20x difference.
This kind of scalable synchronization algorithms inherently requires unsafe package today. This means that it is incompatible with appengine and other safe contexts. While it is not actually unsafe. Looks like a quite unfortunate situation to me. Today people use 80-code machines, tomorrow they will use 200-core machines. Mutexes don’t work there in any way, shape or form.
If Go wants to continue to be positioned as “the way to program modern multicore machines” (which I believe it was initially), then it must provide relevant means for that and its base libraries must not impose scalability bottlenecks.