自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Go 語(yǔ)言實(shí)現(xiàn)安全計(jì)數(shù)的若干種方法

開(kāi)發(fā) 后端
有一天,我正研究共享計(jì)數(shù)器的簡(jiǎn)單經(jīng)典實(shí)現(xiàn),實(shí)現(xiàn)方式使用的是 C++ 中的互斥鎖,這時(shí),我非常想知道還有哪些線程安全的實(shí)現(xiàn)方式。

[[412994]]

本文轉(zhuǎn)載自微信公眾號(hào)「Golang來(lái)啦」,作者Seekload。轉(zhuǎn)載本文請(qǐng)聯(lián)系Golang來(lái)啦公眾號(hào)。

原文如下:

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

不要這樣做

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

  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 

代碼上沒(méi)什么特別的地方。我們來(lái)測(cè)試下結(jié)果正確與否:創(chuàng)建 100 個(gè) goroutine,其中三分之二的 goroutine 對(duì)共享計(jì)數(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.  
  24.  wg.Wait() 
  25.  
  26.  if counter.Read() != 66 { 
  27.   t.Errorf("counter should be %d and was %d", 66, counter.Read()) 
  28.  } 

測(cè)試的結(jié)果是不確定的,有時(shí)候能正確運(yùn)行,有時(shí)候會(huì)出現(xiàn)類似這樣的錯(cuò)誤:

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

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

實(shí)現(xiàn)一個(gè)正確計(jì)數(shù)器的傳統(tǒng)方式是使用互斥鎖,保證任意時(shí)間只有一個(gè)協(xié)程操作計(jì)數(shù)器。Go 語(yǔ)言的話,我們可以使用 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 

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

使用 channel

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

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

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

下面是計(jì)數(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 

當(dāng)一個(gè)協(xié)程調(diào)用 Add(),就往隊(duì)列里面添加一個(gè)寫(xiě)操作:

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

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

  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 

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

原子方式

我們甚至可以用更低級(jí)別的原語(yǔ),利用 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) 

比較和交換

或者,我們可以使用非常經(jīng)典的原語(yǔ):CAS,對(duì)計(jì)時(shí)器進(jìn)行計(jì)數(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) 

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

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

在視頻里,Bj?rn Rabenstein 介紹了如何通過(guò)將浮點(diǎn)數(shù)存儲(chǔ)為 uint64 并使用 math.Float64bits 和 math.Float64frombits 在 float64 和 uint64 之間進(jìn)行轉(zhuǎn)換來(lái)解決此問(wè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)) 

最后

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

本文提到的實(shí)現(xiàn)方式對(duì)應(yīng)的代碼可以看這里[2],此外還包括運(yùn)行用例和基準(zhǔn)測(cè)試。

參考資料

[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

四哥水平有限,如有翻譯或理解錯(cuò)誤,煩請(qǐng)幫忙指出,感謝!

 

責(zé)任編輯:武曉燕 來(lái)源: Golang來(lái)啦
相關(guān)推薦

2022-05-19 14:14:26

go語(yǔ)言限流算法

2023-05-08 07:55:05

快速排序Go 語(yǔ)言

2020-08-12 08:56:30

代碼凱撒密碼函數(shù)

2022-11-01 18:29:25

Go語(yǔ)言排序算法

2024-08-29 13:23:04

WindowsGo語(yǔ)言

2024-06-06 09:47:56

2012-03-13 10:40:58

Google Go

2021-07-12 15:50:55

Go 語(yǔ)言netstat命令

2012-08-06 08:50:05

Go語(yǔ)言

2021-03-01 18:35:18

Go語(yǔ)言虛擬機(jī)

2021-03-01 21:59:25

編程語(yǔ)言GoCX

2014-12-26 09:52:08

Go

2024-08-26 14:32:43

2023-07-31 08:01:13

二叉搜索測(cè)試

2022-04-18 10:01:07

Go 語(yǔ)言漢諾塔游戲

2023-03-27 00:20:48

2024-06-11 00:05:00

CasaOS云存儲(chǔ)管理

2023-08-15 08:01:07

Go 語(yǔ)言排序

2023-01-30 08:16:39

Go語(yǔ)言Map

2022-07-20 09:52:44

Go語(yǔ)言短信驗(yàn)證碼
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)