go高并发之锁的概念(资源竞争)
本文纯享版
案例:
package main
import (
"fmt"
"time"
)
//goroutine map 出现资源竞争
//需求分析:
/*
计算1-200的各个数的阶层,并且把各个数的阶层放入map中,最后遍历输出
要求使用goroutine完成
*/
var maps = make(map[int]int, 200)
func fac(n int) {
res := 1
for i := 1; i <= n; i++ {
res = res * i
}
maps[n] = res
}
func main() {
//计算1-200的阶乘
for n := 1; n <= 200; n++ {
go fac(n)
}
time.Sleep( 10 * time.Second )
for index, value := range maps {
fmt.Printf("maps[%v] = %v", index, value)
}
}
致命错误:
fatal error: concurrent map writes |
原因分析
200个协程同时向一块map空间写入数据
导致资源竞争的问题
于是了锁的概念
思路分析:
var (
maps = make(map[int]int, 200)
//加锁
// lock是一个全局的互斥锁
//mutex是互斥
lock sync.Mutex
)
func fac(n int) {
res := 1
for i := 1; i <= n; i++ {
res = res * i
}
//写之前加锁
lock.Lock()
maps[n] = res
//写完解锁
lock.Unlock()
}
加锁的机制保证了线程的安全
但是我们发觉的问题有:
不知道协程什么时候结束
因为在go中,主线程是不等待协程的
所以两者之间必须要有个通信机制
使用time.Sleep的做法很蠢,也不方便
于是
channel出场
前面使用全局变量加锁同步来解决goroutine的通讯,不太完美
主线程在等待所有的协程全部完成的时间很难确定
通过全局变量加锁同步来实现通讯,不适用于多个协程对全局变量的读写操作
于是有了新的通讯机制channel
channel是线程安全的
当多个协程操作同一个管道时,不会发生资源竞争的问题
数据是先进先出的
队列
入门案例
package main
import (
"fmt"
)
//管道的使用
func main() {
intChan := make(chan int, 10)
for i := 0; i < 10; i++ {
//把i放进管道中
intChan <- i
}
var arrayChan [10]int
for i := 0; i < 10; i++ {
//把管道中的数据输出
arrayChan[i] = <- intChan
}
fmt.Println(arrayChan)
}
输出
[0 1 2 3 4 5 6 7 8 9] |
符合先进先出的规则
并且管道的大小是取一个少一个
容量是不发生变化的
通过去查看通道的长度便知道了
案例:使用通道解决上述的求阶乘的问题
package main
import "fmt"
var intChan = make(chan int, 1)
func fac(n int) {
res := 1
for i := 1; i <= n; i++ {
res = res * i
}
intChan <- res
}
func main() {
//计算1-20的阶乘
for n := 1; n <= 20; n++ {
go fac(n)
resChan := <- intChan
fmt.Printf("%d! = %d\n", n, resChan)
}
}
只需给intChan一个长度即可
程序中每放进一个intChan
就读取一个intChan
节省了空间
也节省了时间
经测试
算200000个数的阶层
使用goroutine + channel 使用的时间是 646ms
而使用普通的的代码
计算阶层的时间是40s