Contents

go标准库-Sync

go语言中文网

godoc

go语言中文网有很多文档缺少内容比如string.Builder就没有,godoc绝对详尽,推荐阅读godoc

type Locker interface

方法

1
2
3
4
type Locker interface {
    Lock()
    Unlock()
}

type Once struct

使用该类型对象多次调用Do方法只执行一次

方法

  • func (o *Once) Do(f func())

type Mutex struct

go专家编程

互斥锁,可以由不同的线程加锁和解锁

属性

  • state int32:状态,是否被锁定以及是否有等待的goroutine
  • sema uint32:表示信号量,这里的信号量是由go编译器在用户态实现的,这个信号量是go编译器自己实现的跟内核空间的信号量无关

方法

  • func (m *Mutex) Lock()
  • func (m *Mutex) Unlock()

上锁:判断Locked标志位是否为0,如果是0则把Locked位置1,代表加锁成功。否则Waiter计数器增加1,并被阻塞

解锁:将Locked位置0,如果Waiter>0,释放一个信号量,唤醒一个阻塞的协程

自旋过程:尝试加锁的协程并不是马上转入阻塞,而是会持续的探测Locked位是否变为0。自旋时间很短,在自旋过程中发现锁已被释放,那么协程可以立即获取锁。自旋对应于CPU的”PAUSE”指令,CPU对该指令什么都不做,相当于CPU空转,对程序而言相当于sleep了一小段时间,时间非常短,当前实现是30个时钟周期

自旋条件:

  • 自旋次数要足够小,通常为4,即自旋最多4次
  • CPU核数要大于1,否则自旋没有意义,因为此时不可能有其他协程释放锁
  • 协程调度机制中的Process数量要大于1,比如使用GOMAXPROCS()将处理器设置为1就不能启用自旋
  • 协程调度机制中的可运行队列必须为空,否则会延迟协程调度

starvation模式:饥饿模式下,不会启动自旋过程

Woken状态:在加锁的协程可能在自旋过程中,此时把Woken标记为1,用于通知解锁协程不必释放信号量了

使用defer避免死锁:加锁后立即使用defer对其解锁,可以有效的避免死锁

加锁和解锁应该成对出现:加锁和解锁最好出现在同一个层次的代码块中,比如同一个函数。重复解锁会引起panic,应避免这种操作的可能性

type RWMutex struct

读写互斥锁,可以被多个读取者持有或唯一写入者持有

属性

  • w Mutex:写锁
  • writerSem uint32:写阻塞信号量
  • readerSem uint32:读阻塞信号量
  • readerCount int32:读goroutine个数
  • readerwait int32:写阻塞时读goroutine个数

方法

  • func (rw *RWMutex) Lock()
  • func (rw *RWMutex) Unlock()
  • func (rw *RWMutex) RLock()
  • func (rw *RWMutex) RUnlock()

type WaitGroup struct

WaitGroup用于等待一组协程的结束。父线程调用Add方法来设定应等待的协程的数量。每个被等待的协程在结束时应调用Done方法。同时,主协程里可以调用Wait方法阻塞至所有协程结束。

注意不要在goroutine里面调用Add方法,只能在主协程里面调用

方法

  • func (wg *WaitGroup) Add(delta int)
  • func (wg *WaitGroup) Done()
  • func (wg *WaitGroup) Wait()

type Map struct

方法

  • func (m *Map) Store(key, value any):存值
  • func (m *Map) Delete(key any):删除值
  • func (m *Map) LoadAndDelete(key any) (value any, loaded bool):获取值没有则存储
  • func (m *Map) Load(key any) (value any, ok bool):获取值

CSDN 一把把把把住了

read 和 dirty 两个字段将读写分离

  • 读取时会先查询 read,不存在再查询 dirty,写入时则只写入 dirty
  • 读取 read 并不需要加锁,而读或写 dirty 都需要加锁
  • misses 字段来统计 read 被穿透的次数(被穿透指需要读 dirty 的情况),超过一定次数则将 dirty 数据同步到 read 上
  • 对于删除数据则直接通过标记来延迟删除

type Cond struct

Cond实现了一个条件变量,必须配合锁使用

变量

1
2
3
4
5
6
7
8
9
type Cond struct {
	noCopy noCopy

	// L is held while observing or changing the condition
	L Locker

	notify  notifyList
	checker copyChecker
}

函数

  • func NewCond(l Locker) *Cond:使用锁l创建一个 *Cond

方法

  • func (c *Cond) Wait():当前goroutine必须已经给c.L上锁,自行解锁并阻塞当前goroutine,只能被Signal或Broadcast方法唤醒
  • func (c *Cond) Signal():唤醒一个goroutine,令其尝试获取锁
  • func (c *Cond) Broadcast():唤醒所有goroutine,令他们尝试获取锁
 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
package main

import (
	"fmt"
	"sync"
	"time"
)

func main() {
	c := sync.NewCond(&sync.Mutex{})
	wg := sync.WaitGroup{}
	wg.Add(2)
	go func() {
		c.L.Lock()
		time.Sleep(2 * time.Second)
		fmt.Println("我是g1要释放锁了")
		c.Wait()
		fmt.Println("aaaa")
		c.L.Unlock()
		wg.Done()
	}()
	go func() {
		c.L.Lock()
		fmt.Println("bbbb")
		time.Sleep(6 * time.Second)
		fmt.Println("我是g2我要释放锁了")
		c.L.Unlock()
		wg.Done()
	}()
	time.Sleep(4 * time.Second)
	fmt.Println("我是main,我要提醒g1获取锁了")
	c.Signal()
	wg.Wait()
}

atomic

atomic包提供了底层的原子级内存操作,对于同步算法的实现很有用

常用函数

  • func LoadInt32(addr int64) (val int32): 原子性的获取addr的值
  • func StoreInt32(addr int64, val int32): 原子性的将val的值保存到addr
  • func AddInt32(addr int32, delta int32) (new int32): 原子性的将val的值添加到addr并返回新值
  • func SwapInt32(addr int32, new int32) (old int32): 原子性的将val的值添加到addr并返回新值
  • func CompareAndSwapInt32(addr int32, old, new int32) (swapped bool):原子性的比较addr和old,如果相同则将new赋值给*addr并返回真

其他数据类型还有

int32 int64 uint32 uint64 uintptr unsafe.Pointer

 |