20、并发状态-GOLANG

多个goroutine有可能出现使用同一个值的场景,这种情况被称为竞态条件,有可能造成程序报错,两个goroutine同时读取一个相同的事物不会造成竞态条件,只有当一方在写入,另一方无论进行读取还是写入的时候才会产生竞态条件

互斥锁

mutex,互斥锁提供了Lock和Unlock两个方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import "sync"

// 网络爬虫,这种类型的数据可以被多个goroutine引用
type Visited struct {
mu sync.Mutex //互斥锁类型的结构元素,负责保护数据
visited map[string]int //map类型的元素,负责记录统计数据
}

func (v Visited) VisitedLink(url string) int {
v.mu.Lock() //上锁
defer v.mu.Unlock() //在返回时解锁
count := v.visited[url]
count++
v.visited[url] = count
return count
}
func main() {

}

隐患

如果一个goroutine在锁定互斥锁之后被阻塞了,想要获取这个数值的其它goroutine纪要等待相当长的一段时间。更严重的是如果持有互斥锁的goroutine尝试锁定同一个互斥锁,那么就会引发死锁

长时间运行的工作机制

场景描述,利用goroutine实现一台好奇号火星探测器的各个模块,用于探索一个虚假的火星,我们将对探测器坐标的变量进行更新,并且还需要探测器对外部的命令进行响应

第一步,我们需要启动一个goroutine负责探测好奇号的位置,这个goroutine会随着探测器软件一同启动,直到被关闭,这种一直存在并且独立运行的goroutine称为工作进程(worker),工作进程通常被携程包含select语句的for循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
package main

import (
"fmt"
"image"
"log"
"time"
)

type command int

const (
right = command(0)
left = command(1)
)

type RoverDriver struct {
commandc chan command
}

func NewRoverDriver() *RoverDriver {
r := &RoverDriver{
commandc: make(chan command),
}
go r.drive()
return r
}
func (r *RoverDriver) drive() {
pos := image.Point{0, 0}
direction := image.Point{1, 0}
updateInterval := 250 * time.Millisecond
nextMove := time.After(updateInterval)
for {
select {
case c := <-r.commandc:
switch c {
case right:
direction = image.Point{-direction.Y, direction.Y}
case left:
direction = image.Point{direction.Y, -direction.X}
}
log.Printf("new direction %v", direction)
case <-nextMove:
pos = pos.Add(direction)
log.Printf("Move to%v", pos)
nextMove = time.After(updateInterval)

}
}
}
func (r *RoverDriver) Left() {
r.commandc <- left
}
func (r *RoverDriver) Right() {
r.commandc <- right
}

func worker() {
pos := image.Point{10, 10} //当前位置,初始值为【10,10】
direction := image.Point{1, 0} //当前方向,初始值为【1,0】向东
next := time.After(time.Second)
for {
select {
case <-next:
pos = pos.Add(direction)
fmt.Println("current position is", pos)
next = time.After(time.Second)
}
}
}
func main() {
r := NewRoverDriver()
time.Sleep(3 * time.Second)
r.Left()
time.Sleep(3 * time.Second)
r.Right()
time.Sleep(3 * time.Second)
}