這句話是推薦使用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()能操作內存,達到了 "讓同一塊內存在同一時間內只被一個線程操作" 的目的
