假如咱们没有用协程通道或者加锁的方式,直接并发使用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虽然看似能够同时保持链接,但发出去的命令在服务器看来是序列化执行的,
所以对服务端来讲,并不存在并发问题!!!