Golang 原子操作與互斥鎖

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

叮~🔔

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章