我在 Go 中實作了以下計數器,我想同時使用它。我正在使用 atomic 包來存盤狀態,但不確定是否會遇到任何競爭條件。例如,我是否還需要添加額外的互斥鎖以防止增量低于零,或者原子操作是否提供足夠的安全性?謝謝!
type Counter struct {
counter uint64
finished uint32
sync.Mutex
}
// Inc increments the counter by one
func (c *Counter) Inc() error {
if c.isFinished() {
return fmt.Errorf("counter is finished")
}
atomic.AddUint64(&c.counter, 1)
return nil
}
// Dec decrements the counter by one, but prevents the counter from going to zero
func (c *Counter) Dec() {
// prevent overflow
if !c.isZero() {
atomic.AddUint64(&c.counter, ^uint64(0))
}
}
// Cancel sets the finished flag, and sets counter to zero
func (c *Counter) Cancel() {
if !c.isFinished() {
atomic.StoreUint32(&c.finished, 1)
atomic.StoreUint64(&c.counter, 0)
}
}
// isFinished returns true if finished
func (c *Counter) isFinished() bool {
return atomic.LoadUint32(&c.finished) == uint32(1)
}
// isZero returns true if counter is zero
func (c *Counter) isZero() bool {
return atomic.LoadUint64(&c.counter) == uint64(0)
}
更新:通過使用-race標志運行下面的代碼,我能夠檢測到我確實需要包含互斥鎖。
var counter *Counter = &Counter{}
func main() {
wg := sync.WaitGroup{}
numberOfLoops := 10
wg.Add(numberOfLoops)
for i := 0; i < numberOfLoops; i {
go incrementor(&wg)
}
wg.Wait()
fmt.Println("Final Counter:", counter.counter)
}
func incrementor(wg *sync.WaitGroup) {
rand.Seed(time.Now().UnixNano())
for i := 0; i < 20; i {
counter.Inc()
time.Sleep(time.Duration(rand.Intn(3)) * time.Millisecond)
}
wg.Done()
counter.Cancel()
}
游樂場鏈接
uj5u.com熱心網友回復:
- 避免“由比賽檢測器檢測到”中的比賽:
正如@caveman 所回答的那樣,您遇到的問題與wg.Done() / wg.Wait()指令排序中的問題有關,并且您不使用 anatomic.Load()來訪問counter.counter.
對于這種競爭條件,您的方法是“安全的”。
- 為了避免“不要達到反擊的不連貫狀態”中的比賽:
你確實有一個問題,(*) 因為你的方法運行了幾個連續的指令來檢查和更新物件(例如:)if condition { update_fields },并且你沒有同步機制來檢查condition當你申請時仍然是真的update_fields。
通過將您的incrementor功能更改為:
func incrementor(wg *sync.WaitGroup) {
for i := 0; i < 20000; i {
counter.Inc()
}
counter.Cancel()
wg.Done()
}
并多次運行您的程式,您應該能夠看到“Final Counter:”并不總是以 0 ( playground )結尾。
這是如何發生的說明:
- 假設 goroutine 1 執行
counter.Cancel() - 當 goroutine 2 執行時
counter.Inc()
可能會發生以下執行順序:
goroutine 1 goroutine 2
1. if c.isFinished() {
return fmt.Errorf("counter is finished")
}
2. if !c.isFinished() {
3. atomic.StoreUint32(&c.finished, 1)
4. atomic.StoreUint64(&c.counter, 0)
}
5. atomic.AddUint64(&c.counter, 1)
6. return nil
- 中的
c.isFinished()指令.Inc()可能在.Cancel()執行之前發生, - 并且
atomic.AddUint64(&c.counter, 1)可能在.Cancel()將計數器重置為零之后發生。
為了避免這種競爭,您需要選擇一種同步inspect update指令的方式。
一種常見的方法是使用互斥鎖:
type Counter struct {
counter uint64
finished uint32
m sync.Mutex
}
// Inc increments the counter by one
func (c *Counter) Inc() error {
c.m.Lock()
defer c.m.Unlock()
if c.finished != 0 {
return fmt.Errorf("counter is finished")
}
c.counter
return nil
}
// Dec decrements the counter by one, but prevents the counter from going to zero
func (c *Counter) Dec() {
c.m.Lock()
defer c.m.Unlock()
// prevent overflow
if c.counter > 0 {
c.counter--
}
}
// Cancel sets the finished flag, and sets counter to zero
func (c *Counter) Cancel() {
c.m.Lock()
defer c.m.Unlock()
if c.finished == 0 {
c.finished = 1
c.counter = 0
}
}
操場
(*) [編輯]我最初寫道:“因為您的方法檢查物件的兩個不同欄位”,但是即使只有一個欄位,您也可能遇到類似的問題。
例如,如果兩個 goroutine 運行以下代碼:
if c.LoadUint64(&c.counter) == 0 {
atomic.AddUint64(&c.counter, 1)
}
您的最終值可能為 2。
問題在于有多條指令,這些指令不是以原子方式執行的。
uj5u.com熱心網友回復:
您不需要額外的互斥鎖,您的主要功能是在counter.counter不使用原子負載的情況下進行讀取,而您在之前incrementor呼叫,因此您會遇到競爭條件。wg.Done()counter.Cancel()
通過wg.Done()在counter.Cancel()競爭條件解決后移動:
func main() {
wg := sync.WaitGroup{}
numberOfLoops := 10
wg.Add(numberOfLoops)
for i := 0; i < numberOfLoops; i {
go incrementor(&wg)
}
wg.Wait()
fmt.Println("Final Counter:", counter.counter)
}
func incrementor(wg *sync.WaitGroup) {
rand.Seed(time.Now().UnixNano())
for i := 0; i < 20; i {
counter.Inc()
time.Sleep(time.Duration(rand.Intn(3)) * time.Millisecond)
}
counter.Cancel()
wg.Done()
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/404495.html
標籤:
上一篇:當只知道一半的密鑰時解組JSON
