Golang 原子操作與互斥鎖
先來看一個代碼
package main
import (
"fmt"
"runtime"
"sync"
)
var (
counter int32
wg sync.WaitGroup
)
func main() {
wg.Add(2) // 設置需要等待的 goroutine 的數量爲 2
go addCounter("Gerald")
go addCounter("Seligman")
wg.Wait()
fmt.Println("Final Counter is: %+v", counter)
}
func addCounter(whoAmI string) {
defer wg.Done()
for count := 0; count < 2; count++ {
value := counter
// Gosched使當前go程放棄處理器,以讓其它go程運行。它不會掛起當前go程,因此當前go程未來會恢復執行。
runtime.Gosched()
value++
counter = value
}
}
Final Counter is: 2
首先這個程序是起了兩個 goroutine 。每個 goroutine 都對 counter 進行了兩次累加操作,所以理論上 counter 最後應該是 4 而不是 2 。
原因是因爲,Gerald 協程獲取到 counter 的值後,又讓 Seligman 協程去獲取 counter 的值。注意此時 counter 的值還沒改變,所以他們兩個協程都拿到 0 這個值。然後兩個協程又將 value++ 後賦值給 counter,等於說做了兩次 counter = 1 的操作。第二次循環也是一樣的原理。所以最後的值是 2 。
這種情況可以用兩中方式避免
- Atomic 原子操作
- Mutex 互斥鎖
Atomic
for count := 0; count < 2; count++ {
value := counter
// Gosched使當前go程放棄處理器,以讓其它go程運行。它不會掛起當前go程,因此當前go程未來會恢復執行。
runtime.Gosched()
value++
counter = value
}
這段代碼其實有點奇怪,爲什麼給 counter + 1 要寫得這麼多步。這其實是在模擬 CPU 內部再給你個值+1的時候需要做的操作。首先寄存器會把 counter 的值拿出來保存在寄存器裏,然後在寄存器裏進行+1操作,最後再把寄存器裏的值放到CPU裏(原理是這樣,若位置錯誤,請及時提醒,見諒)。
而 Atomic 就是相當於在 CPU 里加鎖,讓這三步操作在執行的時候,當前協程不可以被調度切換,等這三步完成之後纔可以被調度切換。
package main
import (
"fmt"
"runtime"
"sync"
"sync/atomic"
)
var (
counter int32
wg sync.WaitGroup
)
func main() {
wg.Add(2) // 設置需要等待的 goroutine 的數量爲 2
go addCounter("Gerald")
go addCounter("Seligman")
wg.Wait()
fmt.Println("Final Counter is: ", counter)
}
func addCounter(whoAmI string) {
defer wg.Done()
for count := 0; count < 2; count++ {
//value := counter
//
//// Gosched使當前go程放棄處理器,以讓其它go程運行。它不會掛起當前go程,因此當前go程未來會恢復執行。
//runtime.Gosched()
//
//value++
//
//counter = value
atomic.AddInt32(&counter, 1)
runtime.Gosched()
}
}
Final Counter is: 4
這次 counter 就變成 4 了,符合預期。
Mutex
這個應該很熟悉了,互斥鎖,就是在對元素操作之前進行加鎖,操作完了解鎖。就可以保證數據一致了。
package main
import (
"fmt"
"sync"
)
var (
counter int32
wg sync.WaitGroup
mutex sync.Mutex
)
func main() {
wg.Add(2) // 設置需要等待的 goroutine 的數量爲 2
go addCounter("Gerald")
go addCounter("Seligman")
wg.Wait()
fmt.Println("Final Counter is: ", counter)
}
func addCounter(whoAmI string) {
defer wg.Done()
for count := 0; count < 2; count++ {
//value := counter
//
//// Gosched使當前go程放棄處理器,以讓其它go程運行。它不會掛起當前go程,因此當前go程未來會恢復執行。
//runtime.Gosched()
//
//value++
//
//counter = value
//atomic.AddInt32(&counter, 1)
//runtime.Gosched()
mutex.Lock()
counter++
mutex.Unlock()
}
}
Final Counter is: 4
叮~🔔