假如我们没有用协程通道或者加锁的方式,直接并发使用map,会出现线性不安全
例如:
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。
反正涉及到并发安全性的数据结构,尽量使用协程通道:发送一个数据到Channel 和 从Channel接收一个数据 都是 原子性的。
可以认为加锁的map就是erlang里面的ets,而使用协程通道就是erlang里面的进程里的数据结构
最后我们来看一下go对redis的并发操作:
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虽然看似可以同时保持连接,但发出去的命令在服务器看来是序列化执行的,
因此对服务端来说,并不存在并发问题!!!