协程并发(并行)资源竞争问题
- 先看需求
- 需求:现在要计算 1-200 的各个数的阶乘,并且把各个数的阶乘放入到 map 中。最后显示出来。
- 要求使用 goroutine 完成
- 分析思路:
- 1) 使用 goroutine 来完成,效率高,但是会出现并发/并行安全问题.
- 2) 这里就提出了不同 goroutine 如何通信的问题
- 代码实现
- 1) 使用 goroutine 来完成(看看使用 gorotine 并发完成会出现什么问题? 然后去解决)
- 2) 在运行某个程序时,如何知道是否存在资源竞争问题。在编译该程序时,增加一个参数 -race 即可
package main import ( "fmt" "time" ) // 思路 // 1. 编写一个函数,来计算各个数的阶乘,并放入到 map中. // 2. 我们启动的协程多个,统计的将结果放入到 map中 // 3. map 应该做出一个全局的. var ( myMap = make(map[int]int, 10) ) // test 函数就是计算 n!, 让将这个结果放入到 myMap func test(n int) { res := 1 for i := 1; i <= n; i++ { res *= i } } func main() { // 这里开启多个协程完成这个任务[200个] for i := 1; i <= 20; i++ { go test(i) } //休眠10秒钟【第二个问题 】 time.Sleep(time.Second * 5) for i, v := range myMap { fmt.Printf("map[%d]=%d\n", i, v) } }
- 输出结果:
- 资源竞争问题示意图解析:
- go build -race main.go
不同 goroutine 之间如何通讯
- 全局变量的互斥锁
- 使用管道 channel 来解决
使用全局变量加锁同步改进程序
- 示意图:
- 因为没有对全局变量 m 加锁,因此会出现资源争夺问题,代码会出现错误,提示 concurrent mapwrites
- 解决方案:加入互斥锁
- 由于数的阶乘很大,结果会越界,可以将求阶乘改成 uint64(i)
package sync
import "sync"
- sync包提供了基本的同步基元,如互斥锁。
- 除了Once和WaitGroup类型,大部分都是适用于低水平程序线程,高水平的同步使用channel通信更好一些。
type Mutex
type Mutex struct { // 包含隐藏或非导出字段 }
- Mutex是一个互斥锁,可以创建为其他结构体的字段;零值为解锁状态。
- Mutex类型的锁和线程无关,可以由不同的线程加锁和解锁。
func (*Mutex) Lock
func (m *Mutex) Lock()
- Lock方法锁住m,如果m已经加锁,则阻塞直到m解锁。
func (*Mutex) Unlock
func (m *Mutex) Unlock()
- Unlock方法解锁m,如果m未加锁会导致运行时错误。
- 锁和线程无关,可以由不同的线程加锁和解锁。
package main import ( "fmt" "time" "sync" ) var ( myMap = make(map[int]int, 10) //声明一个全局的互斥锁 //lock 是一个全局的互斥锁, //sync 是包: synchornized 同步 //Mutex : 是互斥 lock sync.Mutex ) // test 函数就是计算 n!, 让将这个结果放入到 myMap func test(n int) { res := 1 for i := 1; i <= n; i++ { res *= i } lock.Lock() myMap[n] = res //concurrent map writes? //解锁 lock.Unlock() } func main() { // 我们这里开启多个协程完成这个任务[200个] for i := 1; i <= 20; i++ { go test(i) } //休眠10秒钟【第二个问题 】 time.Sleep(time.Second * 5) //这里我们输出结果,变量这个结果 lock.Lock() for i, v := range myMap { fmt.Printf("map[%d]=%d\n", i, v) } lock.Unlock() }