开发者学堂课程【Go 语言核心编程 - 面向对象、文件、单元测试、反射、TCP 编程:全局互斥解锁资源竞争】学习笔记,与课程紧密联系,让用户快速学习知识。
课程地址:https://developer.aliyun.com/learning/course/626/detail/9752
全局互斥解锁资源竞争
内容介绍
一、channel(管道)-看个需求
二、channel(管道)-基本介绍
三、具体代码
一、channel(管道)-看个需求
需求:
现在要计算1-200的各个数的阶乘,并且把各个数的阶乘放入到 map 中。最后显示出来。要求使用 goroutine 完成
1、分析思路:
(1)使用 goroutine 来完成,效率高,但是会出现并发/并行安全问题.
(2)这里就提出了不同 goroutine 如何通信的问题
2、代码实现:
(1)使用 goroutine 来完成(看看使用 goroutine 并发完成会出现什么问题?然后我们会去解决)
(2)在运行某个程序时,如何知道是否存在资源竞争问题。方法很简单,在编译该程序时,增加一个参数-race 即可
D:
\
go project\src \go_code\go build-race test.go
D:\goproject\src\go_code >test.exe
Found 2 data race
二、channel(管道)-基本介绍:
1、不同 goroutine 之间如何通讯(解决方法)
(1)全局变量加锁同步
(2)使用管道 channel 来解决
问题在于:协程1、协程2、协程3、协程4都有可能共同来操纵 map,假设在图示位置有一把锁,当任何一个协程来操纵空间时,先看锁有没有被关闭(lock),如果他是关闭的,就需要进行等待,也就是说对它进行了排队的处理。
改进:
协程1先持有这把锁,得到这把锁后,继续执行,而后解锁(unlock),当协程1正在操作的时候,协程2先去看锁的情况,发现加锁后,先进行队列缓冲,等待协程1,以此类推,协程3排在协程2后面,进而解决问题。
2、使用全局变量加锁同步改进程序
(1)因为没有对全局变量 m 加锁,因此会出现资源争夺问题,代码会出现错误,提示 concurrent map writes
(2)解决方案:加入互斥锁
(3)我们的数的阶乘很大,结果会越界,可以将求阶乘改成 sum += uint64(i)
(4)代码改进:
<
1> var (
myMap =make(map[int]int, 10)
//声明一个全局的互斥锁
//1ock 是一个全局的互斥锁,
//sync 是包:synchronized 同步
//Mutex:是互斥
lock sync.Mutex
)
<2>// test 函数就是计算 n!,让将这个结果放入到 myMap
func test(n int) {
res :=1
for i :=1;i <=n; i++{
res * = i
}
<3>//这里我们将 res 放入到 myMap
//加锁
lock.Lock()
myMap[n] = res //concurrent map writes?
//解锁
lock.Unlock()
}
编译结果如下:
改写为:
//主线程休眠10秒,为的是让协程完成任务
//如果没有这句话,主线程很快就退出 m 中还没有结果呢
time .sleep(10*time.Second)
lock.Lock()
for k,v:=range m {
fmt.Printf(“%d!=%v\n”,k,v)
}
lock.Unlock()
编译结果为:
<4>//这里我们输出结果,变量这个结果
lock-L
ock()
for i
,
v:-range myMap {
fmt.Printf(“map[%d]=%d\n”,i,v)
}
l
ock.Unlock()
3、 channel(管道)-基本介绍
使用全局变量加锁同步改进程序
但这里有一个间题需要给大家说明
//主线程休眠10秒,为的是让协程完成任务
//如果没有这句话,主线程很快就退出 m 中还没有结果呢
time .sleep(10*time.Second)
lock.Lock()
for k,v:=range m {
fmt.Printf(“%d!=%v\n”,k,v)
}
lock.Unlock()
lock.Lock()、lock.Unlock()解释了为什么需要加互斥锁,按理说10秒数上面的协程都应该执行完,后面就不应该出现资源竞争的问题了,但是在实际运行中,还是可能在红框部分出现(运行时增加-race 参数,确实会发现有资源竞争问题),因为我们程序从设计上可以知道10秒就执行完所有协程,但是主线程并不知道,因此底层可能仍然出现资源争夺,因此加入互斥锁即可解决问题。
package sync
import "sync"
三、具体代码
sync 包提供了基本的同步基元,如互斥锁。除了 Once 和 WaitGroup 类型,大部分都是适用于低水平程序钱程,高水平的同步使用 channel 通信更好一些。
不包括的类型的值不应被拷贝
//需求:现在要计算1-200的各个数的阶乘,并且把各个数的阶乘放入到 map 中.
//最后显示出来。要求使用 goroutine 完成
//思路
//1.编写一个函数,来计算各个数的阶乘,并放入到 map 中.
//2.我们启动的协程多个,统计的将结果放入到 map 中
//3. map 应该做出一个全局的.
var (
myMap = make(map[int]int, 10)
//声明一个全局的互斥锁
// lock 是一个全局的互斥锁,
//sync 是包:synchronized 同步
//Mutex :是互斥
lock sync.Mutex
)
//test 函教就是计算 n!,让将这个结果放入到 myMap func test(n int) {
res :=1
for i :=1;i<= n; i++{
res*=i
}
//这里我们将 res 放入到 myMap
//加锁
lock. Lock()
myMap[n] = res //concurrent map writes?
//解锁
lock. Unlock()
}
func main() {
//我们这里开启多个协程完成这个任务[200个]
for i := 1; i<= 200; i++ {
go test(i)
}
//休眠10秒钟【第二个问题】
time.Sleep(time.Second * 10)
//这里我们输出结果,变量这个结果
lock.Lock()
for i, v := range myMap {
fmt.Printf(“map[%d]=%d\n”, i,v)
}
lock. Unlock()
}
使用全局变量方法加锁时的代码改进:
1. 加入互斥锁
2. goroutine 加锁
3. 读取协程时加锁
Map 存放的是 int 类型,存放的数最大为9223372036854775807