假如咱們沒有用協程通道或者加鎖的方式,直接併發使用map,會出現線性不安全redis
例如:安全
package main import ( "time" "fmt" ) var tMap map[int]int func main() { tMap = make(map[int]int) for i := 0; i < 10000; i++ { go putMap(i) } time.Sleep(1e10) fmt.Println("--------------map = ", len(tMap)) } func putMap(i int) { tMap[i] = i }
報錯:服務器
解決方法:數據結構
使用鎖以後就不會有問題:架構
package main import ( "time" "fmt" "sync" ) var tMap map[int]int var mutex sync.Mutex func main() { tMap = make(map[int]int) for i := 0; i < 10000; i++ { go putMap(i) } time.Sleep(1e10) fmt.Println("--------------map = ", len(tMap)) } func putMap(i int) { mutex.Lock() tMap[i] = i mutex.Unlock() }
又或者是利用協程通道,來保證線程安全併發
package main import ( "time" "fmt" ) var tMap map[int]int func main() { tMap = make(map[int]int) data := make(chan int) go goroutine(data) for i := 0; i < 10000; i++ { go putMap(data, i) } time.Sleep(1e10) fmt.Println("--------------map = ", len(tMap)) } func putMap(data chan int, i int) { data <- i } func goroutine(data chan int) { for { i := <- data tMap[i] = i } }
Go的哲學之一就是:不要經過共享內存來通訊,而要經過通訊來共享內存
,前者就是傳統的加鎖
,後者就是Channel。
spa
反正涉及到併發安全性的數據結構,儘可能使用協程通道:發送一個數據到Channel 和 從Channel接收一個數據 都是 原子性的。線程
能夠認爲加鎖的map就是erlang裏面的ets,而使用協程通道就是erlang裏面的進程裏的數據結構code
最後咱們來看一下go對redis的併發操做:server
package main import ( "time" "slg_game_server/server/goredis" "slg_game_server/server/util" ) func main() { for i := 0; i < 10000; i++ { go putRedis(i) } time.Sleep(1e10) } func putRedis(i int) { goredis.ClientRedis.HSet("hb"+util.ToStr(int32(i)), "hb", "hb"+util.ToStr(int32(i))) }
也沒任何問題!!!
由於Redis服務端是個單線程的架構,不一樣的Client雖然看似能夠同時保持鏈接,但發出去的命令在服務器看來是序列化執行的,
所以對服務端來講,並不存在併發問題!!!