日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務時間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
Go語言實現(xiàn)安全計數(shù)的若干種方法

本文轉(zhuǎn)載自微信公眾號「Golang來啦」,作者Seekload。轉(zhuǎn)載本文請聯(lián)系Golang來啦公眾號。

原文如下:

有一天,我正研究共享計數(shù)器的簡單經(jīng)典實現(xiàn),實現(xiàn)方式使用的是 C++ 中的互斥鎖,這時,我非常想知道還有哪些線程安全的實現(xiàn)方式。我通常使用 Go 來滿足自己的好奇心,本文就是一篇如何用 goroutine-safe 的方式實現(xiàn)計數(shù)器的方法匯總。

不要這樣做

我們先從非安全的實現(xiàn)方式開始:

 
 
 
  1. type NotSafeCounter struct {
  2.  number uint64
  3. }
  4. func NewNotSafeCounter() Counter {
  5.  return &NotSafeCounter{0}
  6. }
  7. func (c *NotSafeCounter) Add(num uint64) {
  8.  c.number = c.number + num
  9. }
  10. func (c *NotSafeCounter) Read() uint64 {
  11.  return c.number
  12. }

代碼上沒什么特別的地方。我們來測試下結(jié)果正確與否:創(chuàng)建 100 個 goroutine,其中三分之二的 goroutine 對共享計數(shù)器加一。

 
 
 
  1. func testCorrectness(t *testing.T, counter Counter) {
  2.  wg := &sync.WaitGroup{}
  3.  for i := 0; i < 100; i++ {
  4.   wg.Add(1)
  5.   if i%3 == 0 {
  6.    go func(counter Counter) {
  7.     counter.Read()
  8.     wg.Done()
  9.    }(counter)
  10.   } else if i%3 == 1 {
  11.    go func(counter Counter) {
  12.     counter.Add(1)
  13.     counter.Read()
  14.     wg.Done()
  15.    }(counter)
  16.   } else {
  17.    go func(counter Counter) {
  18.     counter.Add(1)
  19.     wg.Done()
  20.    }(counter)
  21.   }
  22.  }
  23.  wg.Wait()
  24.  if counter.Read() != 66 {
  25.   t.Errorf("counter should be %d and was %d", 66, counter.Read())
  26.  }
  27. }

測試的結(jié)果是不確定的,有時候能正確運行,有時候會出現(xiàn)類似這樣的錯誤:

 
 
 
  1. counter_test.go:34: counter should be 66 and was 65

經(jīng)典實現(xiàn)方式

實現(xiàn)一個正確計數(shù)器的傳統(tǒng)方式是使用互斥鎖,保證任意時間只有一個協(xié)程操作計數(shù)器。Go 語言的話,我們可以使用 sync 包。

 
 
 
  1. type MutexCounter struct {
  2.  mu     *sync.RWMutex
  3.  number uint64
  4. }
  5. func NewMutexCounter() Counter {
  6.  return &MutexCounter{&sync.RWMutex{}, 0}
  7. }
  8. func (c *MutexCounter) Add(num uint64) {
  9.  c.mu.Lock()
  10.  defer c.mu.Unlock()
  11.  c.number = c.number + num
  12. }
  13. func (c *MutexCounter) Read() uint64 {
  14.  c.mu.RLock()
  15.  defer c.mu.RUnlock()
  16.  return c.number
  17. }

現(xiàn)在測試結(jié)果每次都能通過且都是正確的。

使用 channel

鎖是一種保證同步的低級原語。Go 也提供了更高級實現(xiàn)方式 - channel。

關(guān)于 mutexe 和 channel,現(xiàn)在有太多類似這樣的討論:“mutexe vs channel ”、“哪個更好”、“我應當使用哪一個”等。其中一些討論非常有趣且有益,但這并不是本文討論的重點。

我們使用 channel 來實現(xiàn)協(xié)程安全的計數(shù)器,使用 channel 充當隊列,對計數(shù)器的操作(讀、寫)都緩存在隊列中,按順序操作。具體的操作通過傳遞 func() 實現(xiàn)。創(chuàng)建時,計數(shù)器會衍生出一個 goroutine 并且按順序執(zhí)行隊列里的操作。

下面是計數(shù)器的定義:

 
 
 
  1. type ChannelCounter struct {
  2.  ch     chan func()
  3.  number uint64
  4. }
  5. func NewChannelCounter() Counter {
  6.  counter := &ChannelCounter{make(chan func(), 100), 0}
  7.  go func(counter *ChannelCounter) {
  8.   for f := range counter.ch {
  9.    f()
  10.   }
  11.  }(counter)
  12.  return counter
  13. }

當一個協(xié)程調(diào)用 Add(),就往隊列里面添加一個寫操作:

 
 
 
  1. func (c *ChannelCounter) Add(num uint64) {
  2.  c.ch <- func() {
  3.   c.number = c.number + num
  4.  }
  5. }

當一個協(xié)程調(diào)用 Read(),就往隊列里面添加一個讀操作:

 
 
 
  1. func (c *ChannelCounter) Read() uint64 {
  2.  ret := make(chan uint64)
  3.  c.ch <- func() {
  4.   ret <- c.number
  5.   close(ret)
  6.  }
  7.  return <-ret
  8. }

我真正喜歡這個實現(xiàn)的地方在于,這種按順序執(zhí)行的方式非常的清晰。

原子方式

我們甚至可以用更低級別的原語,利用 sync/atomic 包執(zhí)行原子操作。

 
 
 
  1. type AtomicCounter struct {
  2.  number uint64
  3. }
  4. func NewAtomicCounter() Counter {
  5.  return &AtomicCounter{0}
  6. }
  7. func (c *AtomicCounter) Add(num uint64) {
  8.  atomic.AddUint64(&c.number, num)
  9. }
  10. func (c *AtomicCounter) Read() uint64 {
  11.  return atomic.LoadUint64(&c.number)
  12. }

比較和交換

或者,我們可以使用非常經(jīng)典的原語:CAS,對計時器進行計數(shù)。

 
 
 
  1. func (c *CASCounter) Add(num uint64) {
  2.  for {
  3.   v := atomic.LoadUint64(&c.number)
  4.   if atomic.CompareAndSwapUint64(&c.number, v, v+num) {
  5.    return
  6.   }
  7.  }
  8. }
  9. func (c *CASCounter) Read() uint64 {
  10.  return atomic.LoadUint64(&c.number)
  11. }

float 類型該如何實現(xiàn)

在我探索學習過程中,看到一個非常棒的視頻 - 《Prometheus: Designing and Implementing a Modern Monitoring Solution in Go[1]》。在視頻的最后,討論了如何實現(xiàn)浮點數(shù)計數(shù)器。到目前為止,所有的技術(shù)都適用于浮點數(shù),除了 sync/atomic 包,還沒提供浮點數(shù)的原子操作。

在視頻里,Bj?rn Rabenstein 介紹了如何通過將浮點數(shù)存儲為 uint64 并使用 math.Float64bits 和 math.Float64frombits 在 float64 和 uint64 之間進行轉(zhuǎn)換來解決此問題。

 
 
 
  1. type CASFloatCounter struct {
  2.  number uint64
  3. }
  4. func NewCASFloatCounter() *CASFloatCounter {
  5.  return &CASFloatCounter{0}
  6. }
  7. func (c *CASFloatCounter) Add(num float64) {
  8.  for {
  9.   v := atomic.LoadUint64(&c.number)
  10.   newValue := math.Float64bits(math.Float64frombits(v) + num)
  11.   if atomic.CompareAndSwapUint64(&c.number, v, newValue) {
  12.    return
  13.   }
  14.  }
  15. }
  16. func (c *CASFloatCounter) Read() float64 {
  17.  return math.Float64frombits(atomic.LoadUint64(&c.number))
  18. }

最后

這篇文章是共享計數(shù)器的實現(xiàn)匯總。這是我好奇心驅(qū)使的結(jié)果,此外對并發(fā)也有一個基本的了解。如果你有其他實現(xiàn)共享計數(shù)的方式,請告訴我。

本文提到的實現(xiàn)方式對應的代碼可以看這里[2],此外還包括運行用例和基準測試。

參考資料

[1]Prometheus: Designing and Implementing a Modern Monitoring Solution in Go: https://www.youtube.com/watch?v=1V7eJ0jN8-E

[2]看這里: https://github.com/brunocalza/sharedcounter

via:https://brunocalza.me/there-are-many-ways-to-safely-count/

作者:BRUNO CALZA

四哥水平有限,如有翻譯或理解錯誤,煩請幫忙指出,感謝!


新聞名稱:Go語言實現(xiàn)安全計數(shù)的若干種方法
標題來源:http://m.5511xx.com/article/coceece.html