昨天看過 race condiction 的情況後,我們了解了 mutex.Lock 與 channel,可以幫助我們同步 memory 狀態,避免發生奇怪的資料異常狀態。今天我們要延續昨天的技巧,並且用 benchmark 比較兩個在速度上的優劣。
我們透過測試範例,可以看出 mutex 每次操作僅需 215 ns/op,而 channel 則需 538 ns/op。也就是在一般情況下,如果我們只是想避免變數上的污染,大可放心的使用較簡易的 mutex lock。畢竟他效能佳且使用較為直覺。
> go test -v -bench=. -run=none -benchmem ./basicGo/benchmark/mutex_test.go
goos: darwin
goarch: amd64
BenchmarkMutex
BenchmarkMutex-4 5752076 215 ns/op 0 B/op 0 allocs/op
BenchmarkChannel
BenchmarkChannel-4 2011600 538 ns/op 76 B/op 0 allocs/op
PASS
ok command-line-arguments 3.360s
只看 benchmark 的話,可能會覺得 channel 沒有發揮的空間,但其實不然。在一些較複雜的多 goroutines 情境下,goroutine 各自過 channel 來做資料的傳遞,可以在不同的 goroutine 間傳遞異動資料。
package main
import "fmt"
//Burger 就是個漢堡
type Burger struct {
beef bool
cheese bool
pickles bool
}
var (
addBeff = make(chan Burger, 10) //超過10筆後阻塞
addCheese = make(chan Burger, 10) //超過10筆後阻塞
addPickles = make(chan Burger, 10) //超過10筆後阻塞
done = make(chan []Burger)
)
func main() {
go AddBeff() //加牛肉監聽開始
go AddCheese() //加起司監聽開始
go AddPickles() //加酸黃瓜監聽開始
for {
select {
//達成後停止
case count := <-done:
for i := range count {
if !count[i].beef || !count[i].cheese || !count[i].pickles {
fmt.Println("oops!", count)
return
}
}
fmt.Println(len(count), "success!")
return
//開始製造漢堡
case addBeff <- Burger{}:
}
}
}
//AddBeff 加牛肉
func AddBeff() {
for {
//一次只能加一塊牛肉
select {
case burger := <-addBeff:
burger.beef = true
addCheese <- burger
}
}
}
//AddCheese 加起司
func AddCheese() {
for {
//一次只能加一塊起司
select {
case burger := <-addCheese:
burger.cheese = true
addPickles <- burger
}
}
}
//AddPickles 加酸黃瓜
func AddPickles() {
var count []Burger
for {
//一次只能加一份酸黃瓜
select {
case burger := <-addPickles:
burger.pickles = true
//漢堡完成
count = append(count, burger)
//達成目標
if len(count) == 1000 {
done <- count
}
}
}
}