發現問題
今天在看代碼的時候,遇見了多個協程寫同一個slice的情況,發現未對slice做任何保護,亦未使用其他手段保證並發安全,這樣肯定會出錯的。
思考
slice不是協程安全的,所以在多個協程中讀寫slice是不安全的,在高並發的情況下會產生不可控制的錯誤。
總結
這里記錄一下錯誤的使用方式與正確的使用方式:
錯誤的使用方式:
var a []int for i := 0; i < 10000; i++ { go func() { a = append(a, 1) // 多協程並發讀寫slice }() } fmt.Println(len(a))
輸出結果可能不等於期望的值
正確對方式
第一種方式:
對slice加鎖,進行保護
num := 10000 var a []int var l sync.Mutex var wg sync.WaitGroup wg.Add(num) for i := 0; i < num; i++ { go func() { l.Lock() // 加鎖 a = append(a, 1) l.Unlock() // 解鎖 wg.Done() }() } wg.Wait() fmt.Println(len(a))
缺點:鎖會影響性能
第二種方式:
使用channel的傳遞數據
num := 10000 var wg sync.WaitGroup wg.Add(num) c := make(chan int) for i := 0; i < num; i++ { go func() { c <- 1 // channl是協程安全的 wg.Done() }() } // 等待關閉channel go func() { wg.Wait() close(c) }() // 讀取數據 var a []int for i := range c { a = append(a, i) } fmt.Println(len(a))
第三種方式:
使用索引
num := 10000 a := make([]int, num, num) var wg sync.WaitGroup wg.Add(num) for i := 0; i < num; i++ { k := i // 必須使用局部變量 go func(idx int) { a[idx] = 1 wg.Done() }(k) } wg.Wait() count := 0 for i := range a { if a[i] != 0 { count++ } } fmt.Println(count)
優點:無鎖,不影響性能