這句話是推薦使用channel來實現 "讓同一塊內存在同一時間內只被一個線程操作" 的目的
先看一個簡單的示例代碼
package main import ( "fmt" "net/http" ) var j int func HelloServer(w http.ResponseWriter, req *http.Request) { j++ fmt.Println(j) } func main() { http.HandleFunc("/", HelloServer) http.ListenAndServe(":8080", nil) }
每個http請求都會創建一個新的goroutine,在高並發下,多個goroutine對全局變量 j 進行同時讀寫。有可能造成這么一個局面:
前面一個goroutine在 j++ 之后,此時 j 值為5,本來應該輸出5的,結果輸出之前另一個goroutine又執行了一次 j++ ,這樣一來 j 值就為6,然后兩個goroutine一起輸出了6
下面兩個方法可以解決該問題:
1. 前者 - 【用共享內存來通信】,就是上鎖。將例子中代碼修改如下
package main import ( "sync" "fmt" "net/http" ) var j int var m sync.Mutex // 互斥鎖 func HelloServer(w http.ResponseWriter, req *http.Request) { m.Lock() j++ fmt.Println(j) m.Unlock() } func main() { http.HandleFunc("/", HelloServer) http.ListenAndServe(":8080", nil) }
將用戶要進行業務流程之前上鎖,執行完后解鎖。這樣每次同一時間就只有一個goroutine在操作內存變量了
2. 后者 - 【要用通信來共享內存】,就是使用goroutine + channel,將例子中代碼修改如下
package main import ( "fmt" "net/http" ) var j int var chGoto = make(chan int) func HelloServer(w http.ResponseWriter, req *http.Request) { // 將干活信號傳給goroutine chGoto <- 1 } func rec() { for { // 等待干活信號 <- chGoto // 開始干活 j++ fmt.Println(j) } } func main() { // 先創建一個goroutine go rec() http.HandleFunc("/", HelloServer) http.ListenAndServe(":8080", nil) }
主協程專門創建一個goroutine用來操作內存,創建完畢后rec()堵塞,等待其他goroutine往chGoto傳值,假設http請求A往chGoto傳值,當rec()執行完A傳入的 <- chGoto 之后,另一個http請求B緊接着又會向chGoto傳值,但此時rec()還在執行從A傳值chGoto的for循環里還沒執行的語句,執行完后rec()再從chGoto中取出B的傳值。這樣一來高並發下多個goroutine只有依靠單個rec()能操作內存,達到了 "讓同一塊內存在同一時間內只被一個線程操作" 的目的