8.2 Go 锁
案例(坑):多个goroutine操作同一个map。
go提供了一种叫map的数据结构,可以翻译成映射,对应于其他语言的字典、哈希表。借助map,可以定义一个键和值,然后可以从map中获取、设置和删除这个值,尤其适合数据查找的场景。
但是map的使用有一定的限制,如果是在单个协程中读写map,那么不会存在什么问题,如果是多个协程并发访问一个map
,有可能会导致程序退出,并打印下面错误信息。
fatal error: concurrent map writes
错误案例代码
package main var myMap = make(map[int]int, 10) //要求计算50的阶乘结果 //阶乘就是1*2*3*4...50 =? func test(n int) { //定义初始值1 res := 1 //每次循环进行阶乘 for i := 0; i <= n; i++ { res *= i } //最终计算结果,写入map //由于多个协程同时操作map,引发资源竞争报错 myMap[n] = res } func main() { //开启50个协程 for i := 0; i < 10; i++ { go test(10) } }
并发访问map是不安全的操作,在协程
中访问map,必须提供某种同步资源
机制,使用sync.Mutex
互斥锁同步解决协程的竞争问题。
package main import ( "fmt" "sync" "time" ) var lock sync.Mutex func Printer(str string) { //我现在开始使用打印机了,其他人都等我完事了再来 lock.Lock() for _, data := range str { fmt.Printf("%c", data) time.Sleep(time.Second) } //我完事了,你们上吧 lock.Unlock() fmt.Printf(" ") } func Alex() { Printer("hello") } func Wupeiqi() { Printer("oldboy") } func main() { //coffe(10)//单线程执行函数 go Alex() go Wupeiqi() //主线程等待协程结束后 再退出 time.Sleep(time.Second * 10) }
结论:
1.可以使用加锁的方式解决goroutine的通讯。 2.主线程等待所有goroutine的时间难以确定,设置固定的等待时间肯定不合理。 3.对全局变量加锁同步来通讯,也不利于多个协程对变量的读写。 4.要让主线程等待所有goroutine退出后在退出,如何知道所有goroutine都退出了呢? 5.因此,channel应运而生!!