sync包内主要是一些锁,信号量之类的东西

条件变量 Cond

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
type Cond struct {
noCopy noCopy // noCopy字段,用于标记该结构无法复制。通过go vet命令可以检测代码中是否出现存在noCopy字段的结构却发生复制的情况。
L Locker // 锁接口

notify notifyList // 待通知列表
checker copyChecker // 普通指针,用于运行时检查是否发生复制。运行时检查。检查原理见下方。
}

type copyChecker uintptr

type Locker interface {
Lock() // 锁接口
Unlock() // 解锁接口
}

type notifyList struct {
wait uint32 // 下一个待通知goroutine编号

notify uint32 // 已通知过的goroutine编号

lock mutex // 锁
head *sudog // list 起始位置
tail *sudog // list 结束位置
}

wait

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
func (c *Cond) Wait() {
c.checker.check() // 检查是否发生复制
t := runtime_notifyListAdd(&c.notify) // 获取wait值,并使notifyList.wait值自增。
c.L.Unlock() // 休眠前需要释放锁
runtime_notifyListWait(&c.notify, t) // 添加到notifyList后休眠
c.L.Lock()
}

func notifyListAdd(l *notifyList) uint32 {
return atomic.Xadd(&l.wait, 1) - 1 // 自增后,返回原本的值。 思考一下这里如果wait溢出,变为0会怎么样?
}

func less(a, b uint32) bool {
return int32(a-b) < 0
}

func notifyListWait(l *notifyList, t uint32) {
lock(&l.lock)

if less(t, l.notify) { // 判断是否wait和notify是否间距过大
unlock(&l.lock)
return
}

// Enqueue itself.
s := acquireSudog() // 获取一个gotinue,将自己的信息添加到gotinue中
s.g = getg()
s.ticket = t
s.releasetime = 0
t0 := int64(0)
if blockprofilerate > 0 {
t0 = cputicks()
s.releasetime = -1
}
if l.tail == nil { // 将当前gotinue加入待唤醒队列。
l.head = s
} else {
l.tail.next = s
}
l.tail = s
goparkunlock(&l.lock, "semacquire", traceEvGoBlockCond, 3) // 休眠自身
if t0 != 0 {
blockevent(s.releasetime-t0, 2)
}
releaseSudog(s) // 唤醒后释放s
}

signal

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
func (c *Cond) Signal() {
c.checker.check() // 检测是否发生复制
runtime_notifyListNotifyOne(&c.notify) // 通知并唤醒响应的goroutinue
}

func notifyListNotifyOne(l *notifyList) {
if atomic.Load(&l.wait) == atomic.Load(&l.notify) { // wait == notify, 代表没有待通知的goroutinue
return
}

lock(&l.lock)

// Re-check under the lock if we need to do anything.
t := l.notify
if t == atomic.Load(&l.wait) { // 拿锁在此判断一下
unlock(&l.lock)
return
}

// Update the next notify ticket number.
atomic.Store(&l.notify, t+1) // notify自增

// 遍历待通知的goroutinue
// 遍历到将该goroutinue从链表中移除并且置为可调度状态。未遍历到直接返回。
for p, s := (*sudog)(nil), l.head; s != nil; p, s = s, s.next {
if s.ticket == t {
n := s.next
if p != nil {
p.next = n
} else {
l.head = n
}
if n == nil {
l.tail = p
}
unlock(&l.lock)
s.next = nil
readyWithTime(s, 4) // 将goroutinue置为可调度状态
return
}
}
unlock(&l.lock)
}

copyCheck

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func (c *copyChecker) check() {
/*
1. uintptr(*c) != uintptr(unsafe.Pointer(c)) 指针不指向本身。
( 指针不指向本身有两种情况。a.根据conf初始化逻辑,conf.checkerw为nil。第一次调用会存在指针不指向本身。 b. 发生了复制。


2. !atomic.CompareAndSwapUintptr((*uintptr)(c), 0, uintptr(unsafe.Pointer(c))) cas操作。 这里是判断如果c为nil, 则将c指向c的地址。如果发生了这样的操作,返回值为true。否则返回值为false。 返回值为true的情况下,跳出if语句,这里就杜绝了第一个判断条件为a的情况。 返回值为false的情况下,即c不为nil。

3. intptr(*c) != uintptr(unsafe.Pointer(c) 再次检测指针不指向本身。
结合前两个判断语句,可以说明这里conf发生了复制。
*/
if uintptr(*c) != uintptr(unsafe.Pointer(c)) &&
!atomic.CompareAndSwapUintptr((*uintptr)(c), 0, uintptr(unsafe.Pointer(c))) &&
uintptr(*c) != uintptr(unsafe.Pointer(c)) {
panic("sync.Cond is copied")
}
}