在上一篇介紹了多執行序的好處以及撰寫方式,但是也提到了多執行序的問題,所以這篇針對多執行序遇到的race condition在 go 語言如何解決它來說明。
本文同步放置於此
在程式執行的過程中可能會做一個改變物件屬性的動作,然而在多執行序下會因為這個動作產生一個問題,就是後面執行的會把前面執行的結果給蓋掉。
所以通常的解決方式就是加一個鎖 mutex
讓要更改這物件的執行序排隊來更改,接下來就跟大家介紹如何撰寫這個鎖。
這時還是一樣請大家先看看Go Tour的範例
package main
import (
"fmt"
"sync"
"time"
)
// SafeCounter is safe to use concurrently.
type SafeCounter struct {
v map[string]int
mux sync.Mutex
}
// Inc increments the counter for the given key.
func (c *SafeCounter) Inc(key string) {
c.mux.Lock()
// Lock so only one goroutine at a time can access the map c.v.
c.v[key]++
c.mux.Unlock()
}
// Value returns the current value of the counter for the given key.
func (c *SafeCounter) Value(key string) int {
c.mux.Lock()
// Lock so only one goroutine at a time can access the map c.v.
defer c.mux.Unlock()
return c.v[key]
}
func main() {
c := SafeCounter{v: make(map[string]int)}
for i := 0; i < 1000; i++ {
go c.Inc("somekey")
}
time.Sleep(time.Second)
fmt.Println(c.Value("somekey"))
}
相信有閱讀筆者前幾篇文章可以理解上面的程式在做甚麼,這邊針對 make
關鍵字加以說明。
這個 make
主要的用途就是產生一個記憶體空間給變數,所以簡單講上面的例子就是產生一個key
是字串value
是整數的map
給變數 c 的屬性 v 。
接下來在下一個段落說明一下 mutex
的運作方式。
搭配上面的例子,主要是在 Inc 跟 Value 這兩個方法,因為要避免race condition的問題所以不管在存取前都要加上一個鎖,限制只能有一個執行序來存取變數,而其他未取得鎖的直行序會等待鎖解開之後取得鎖在執行。
所以在新增之前要先呼叫 c.Mutex.Lock()
來取得鎖,並鎖住執行序來更新該變數,等到更新完了之後一定要記得呼叫 c.mux.Unlock()
來解鎖,不然就永遠沒有可以執行存取變數的權限了,而其他執行序也不能繼續執行下去。
因此在 Value 這方法也是需要取的鎖來取的變數的值,正因為需要回傳值,所以這邊用到了 defer
待回傳值之後會呼叫 c.mux.Unlock()
來解鎖。
其實也可以在前面先寫下 defer c.mux.Unlock()
來避免方法結束後沒有解鎖的窘境。
這篇跟大家介紹多執行序如何利用 mutex
來解決race condition的問題,也針對相關語法做進一步說明,希望對大家的 go 多執行序編程能有些幫助。