Golang学习 - sync 包

本文介绍了Go语言中的几种并发工具,包括临时对象池(Pool)、单次执行(Once)、互斥锁(Mutex)、读写互斥锁(RWMutex)、组等待(WaitGroup)和条件等待(Cond),并提供了示例代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

------------------------------------------------------------

临时对象池

  Pool 用于存储临时对象,它将使用完毕的对象存入对象池中,在需要的时候取出来重复使用,目的是为了避免重复创建相同的对象造成 GC 负担过重。其中存放的临时对象随时可能被 GC 回收掉(如果该对象不再被其它变量引用)。

  从 Pool 中取出对象时,如果 Pool 中没有对象,将返回 nil,但是如果给 Pool.New 字段指定了一个函数的话,Pool 将使用该函数创建一个新对象返回。

  Pool 可以安全的在多个例程中并行使用,但 Pool 并不适用于所有空闲对象,Pool 应该用来管理并发的例程共享的临时对象,而不应该管理短寿命对象中的临时对象,因为这种情况下内存不能很好的分配,这些短寿命对象应该自己实现空闲列表。

  Pool 在开始使用之后,不能再被复制。

------------------------------

type Pool struct {
	// 创建临时对象的函数
	New func() interface{}
}

// 向临时对象池中存入对象
func (p *Pool) Put(x interface{})

// 从临时对象池中取出对象
func (p *Pool) Get() interface{}

------------------------------------------------------------

单次执行

  Once 的作用是多次调用但只执行一次,Once 只有一个方法,Once.Do(),向 Do 传入一个函数,这个函数在第一次执行 Once.Do() 的时候会被调用,以后再执行 Once.Do() 将没有任何动作,即使传入了其它的函数,也不会被执行,如果要执行其它函数,需要重新创建一个 Once 对象。

  Once 可以安全的在多个例程中并行使用。

------------------------------

// 多次调用仅执行一次指定的函数 f
func (o *Once) Do(f func())

------------------------------

// 示例:Once
func main() {
	var once sync.Once
	onceBody := func() {
		fmt.Println("Only once")
	}
	done := make(chan bool)
	for i := 0; i < 10; i++ {
		go func() {
			once.Do(onceBody) // 多次调用只执行一次
			done <- true
		}()
	}
	for i := 0; i < 10; i++ {
		<-done
	}
}

// 输出结果:
// Only once

------------------------------------------------------------

互斥锁

  互斥锁用来保证在任一时刻,只能有一个例程访问某对象。Mutex 的初始值为解锁状态。Mutex 通常作为其它结构体的匿名字段使用,使该结构体具有 Lock 和 Unlock 方法。

  Mutex 可以安全的在多个例程中并行使用。

------------------------------

// Locker 接口包装了基本的 Lock 和 UnLock 方法,用于加锁和解锁。
type Locker interface {
    Lock()
    Unlock()
}

// Lock 用于锁住 m,如果 m 已经被加锁,则 Lock 将被阻塞,直到 m 被解锁。
func (m *Mutex) Lock()

// Unlock 用于解锁 m,如果 m 未加锁,则该操作会引发 panic。
func (m *Mutex) Unlock()

------------------------------

// 示例:互斥锁
type SafeInt struct {
	sync.Mutex
	Num int
}

func main() {
	count := SafeInt{}
	done := make(chan bool)
	for i := 0; i < 10; i++ {
		go func(i int) {
			count.Lock() // 加锁,防止其它例程修改 count
			count.Num += i
			fmt.Print(count.Num, " ")
			count.Unlock() // 修改完毕,解锁
			done <- true
		}(i)
	}
	for i := 0; i < 10; i++ {
		<-done
	}
}

// 输出结果(不固定):
// 2 11 14 18 23 29 36 44 45 45

------------------------------------------------------------

读写互斥锁

  RWMutex 比 Mutex 多了一个“读锁定”和“读解锁”,可以让多个例程同时读取某对象。RWMutex 的初始值为解锁状态。RWMutex 通常作为其它结构体的匿名字段使用。

  Mutex 可以安全的在多个例程中并行使用。

------------------------------

// Lock 将 rw 设置为写锁定状态,禁止其他例程读取或写入。
func (rw *RWMutex) Lock()

// Unlock 解除 rw 的写锁定状态,如果 rw 未被写锁定,则该操作会引发 panic。
func (rw *RWMutex) Unlock()

// RLock 将 rw 设置为读锁定状态,禁止其他例程写入,但可以读取。
func (rw *RWMutex) RLock()

// Runlock 解除 rw 的读锁定状态,如果 rw 未被读锁顶,则该操作会引发 panic。
func (rw *RWMutex) RUnlock()

// RLocker 返回一个互斥锁,将 rw.RLock 和 rw.RUnlock 封装成了一个 Locker 接口。
func (rw *RWMutex) RLocker() Locker

------------------------------------------------------------

组等待

  WaitGroup 用于等待一组例程的结束。主例程在创建每个子例程的时候先调用 Add 增加等待计数,每个子例程在结束时调用 Done 减少例程计数。之后,主例程通过 Wait 方法开始等待,直到计数器归零才继续执行。

------------------------------

// 计数器增加 delta,delta 可以是负数。
func (wg *WaitGroup) Add(delta int)

// 计数器减少 1
func (wg *WaitGroup) Done()

// 等待直到计数器归零。如果计数器小于 0,则该操作会引发 panic。
func (wg *WaitGroup) Wait()

------------------------------

// 示例:组等待
func main() {
	wg := sync.WaitGroup{}
	wg.Add(10)
	for i := 0; i < 10; i++ {
		go func(i int) {
			defer wg.Done()
			fmt.Print(i, " ")
		}(i)
	}
	wg.Wait()
}

// 输出结果(不固定):
// 9 3 4 5 6 7 8 0 1 2

------------------------------------------------------------

条件等待

  条件等待通过 Wait 让例程等待,通过 Signal 让一个等待的例程继续,通过 Broadcast 让所有等待的例程继续。

  在 Wait 之前应当手动为 c.L 上锁,Wait 结束后手动解锁。为避免虚假唤醒,需要将 Wait 放到一个条件判断循环中。官方要求的写法如下:

c.L.Lock()
for !condition() {
    c.Wait()
}
// 执行条件满足之后的动作...
c.L.Unlock()

  Cond 在开始使用之后,不能再被复制。

------------------------------

type Cond struct {
    L Locker // 在“检查条件”或“更改条件”时 L 应该锁定。
} 

// 创建一个条件等待
func NewCond(l Locker) *Cond

// Broadcast 唤醒所有等待的 Wait,建议在“更改条件”时锁定 c.L,更改完毕再解锁。
func (c *Cond) Broadcast()

// Signal 唤醒一个等待的 Wait,建议在“更改条件”时锁定 c.L,更改完毕再解锁。
func (c *Cond) Signal()

// Wait 会解锁 c.L 并进入等待状态,在被唤醒时,会重新锁定 c.L
func (c *Cond) Wait()

------------------------------

// 示例:条件等待
func main() {
	condition := false // 条件不满足
	var mu sync.Mutex
	cond := sync.NewCond(&mu)
	// 让例程去创造条件
	go func() {
		mu.Lock()
		condition = true // 更改条件
		cond.Signal()    // 发送通知:条件已经满足
		mu.Unlock()
	}()
	mu.Lock()
	// 检查条件是否满足,避免虚假通知,同时避免 Signal 提前于 Wait 执行。
	for !condition {
		// 等待条件满足的通知,如果收到虚假通知,则循环继续等待。
		cond.Wait() // 等待时 mu 处于解锁状态,唤醒时重新锁定。
	}
	fmt.Println("条件满足,开始后续动作...")
	mu.Unlock()
}

// 输出结果:
// 条件满足,开始后续动作...

------------------------------------------------------------



转载于:https://www.cnblogs.com/golove/p/5918082.html

### Golang 中 `sync` 的使用方法 #### 1. 基本概念 Go 的标准库中,`sync` 提供了一些基础工具来支持多线程环境下的同步操作。这些工具主要用于实现并发控制和数据共享的安全机制[^1]。 #### 2. 主要功能组件 以下是 `sync` 的一些核心功能及其用途: - **Mutex (互斥锁)** Mutex 是一种简单的锁定原语,用于保护临界区代码段不被多个 Goroutine 同时访问。通过调用 `Lock()` 和 `Unlock()` 方法可以分别加锁和解锁。 ```go var mu sync.Mutex func incrementCounter() { mu.Lock() defer mu.Unlock() // 修改共享变量的操作 } ``` - **RWMutex (读写锁)** RWMutex 提供了一种更灵活的方式,在允许多个读者的同时只允许单个写者进入。这适合于频繁读取而较少修改的数据结构场景。 ```go var rwmu sync.RWMutex func readData() { rwmu.RLock() defer rwmu.RUnlock() // 只读操作 } func writeData() { rwmu.Lock() defer rwmu.Unlock() // 数据更新操作 } ``` - **WaitGroup (等待组)** WaitGroup 能够让一个 Goroutine 等待其他一组 Goroutine 完成后再继续执行。它常用来协调主流程与其他子任务之间的关系。 ```go var wg sync.WaitGroup func doWork(id int) { defer wg.Done() fmt.Printf("Worker %d starting\n", id) time.Sleep(time.Second) fmt.Printf("Worker %d done\n", id) } func main() { const numWorkers = 5 wg.Add(numWorkers) for i := 1; i <= numWorkers; i++ { go doWork(i) } wg.Wait() fmt.Println("All workers have finished.") } ``` - **Once (一次性初始化)** Once 类型确保某个动作在整个程序生命周期内仅被执行一次,即使有多个 Goroutine 尝试触发该动作也无妨。 ```go var once sync.Once func initResource() { once.Do(func() { fmt.Println("Initializing resource...") // 初始化逻辑 }) } ``` #### 3. 应用场景与注意事项 虽然 `sync` 提供了丰富的同步手段,但在实际项目开发过程中需要注意以下几点: - 避免死锁情况的发生; - 控制好锁粒度大小以免影响性能; - 对于高阶抽象层次上的协作问题建议优先考虑 Channel 方式而非直接依赖原始同步构件。 #### 4. 学习资料推荐 对于希望深入理解并掌握 Go 并发特性的开发者来说,《Concurrency in Go》是一本非常值得阅读的好书。另外也可以参考官方文档以及社区分享的学习笔记等内容进一步巩固知识点[^2]。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值