golang,go,博客,开源,编程
Go 的 sync
包提供了多个用于同步并发操作的工具,主要包括互斥锁(Mutex)、等待组(WaitGroup)、读写锁(RWMutex)、一次性操作(Once)、条件变量(Cond)等。这些工具使得在多协程并发环境下的共享资源访问更加安全。
下面详细介绍 sync
包中的常用类型和它们的使用。
sync.Mutex
(互斥锁)Mutex
(互斥锁)用于控制对共享资源的访问,确保在同一时刻只有一个 goroutine 可以访问该资源。
package main
import (
"fmt"
"sync"
)
var mu sync.Mutex
var counter int
func increment() {
mu.Lock() // 上锁
defer mu.Unlock() // 解锁
counter++
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
increment()
}()
}
wg.Wait()
fmt.Println("Counter:", counter)
}
mu.Lock()
:加锁,确保当前 goroutine 可以安全地访问共享资源。mu.Unlock()
:解锁,允许其他 goroutine 访问共享资源。defer mu.Unlock()
确保即使函数中途返回,锁也会被正确释放。sync.RWMutex
(读写锁)RWMutex
是一种更细粒度的锁,它允许多个 goroutine 同时读取共享资源,但当有一个 goroutine 写入资源时,其他 goroutine 无法进行读或写操作。它适用于读多写少的场景,可以提高性能。
package main
import (
"fmt"
"sync"
)
var rwmu sync.RWMutex
var counter int
func readCounter() int {
rwmu.RLock() // 获取读锁
defer rwmu.RUnlock() // 释放读锁
return counter
}
func writeCounter(value int) {
rwmu.Lock() // 获取写锁
defer rwmu.Unlock() // 释放写锁
counter = value
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
writeCounter(i)
}()
}
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
fmt.Println(readCounter())
}()
}
wg.Wait()
}
rwmu.RLock()
:加读锁,允许多个 goroutine 同时读取共享资源。rwmu.RUnlock()
:释放读锁。rwmu.Lock()
:加写锁,只有一个 goroutine 可以写入资源。rwmu.Unlock()
:释放写锁。sync.WaitGroup
(等待组)WaitGroup
用于等待一组 goroutine 执行完毕。它主要有以下三种方法:
Add(n int)
:设置等待的 goroutine 数量,通常在启动 goroutine 前调用。Done()
:每个 goroutine 执行完后调用,表示一个 goroutine 完成。Wait()
:阻塞,直到所有 goroutine 执行完毕。package main
import (
"fmt"
"sync"
)
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done()
fmt.Printf("Worker %d started\n", id)
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1) // 每启动一个 goroutine 都要调用一次 Add
go worker(i, &wg)
}
wg.Wait() // 等待所有 goroutine 执行完毕
}
wg.Add(1)
:增加一个等待的 goroutine。wg.Done()
:每个 goroutine 执行完成后调用。wg.Wait()
:主 goroutine 会等待所有其他 goroutine 执行完毕。sync.Once
(一次性操作)Once
类型用于确保某个操作仅执行一次,常用于需要进行单次初始化的场景。Once
会保证该操作只会在并发环境下执行一次,即使有多个 goroutine 同时尝试执行它。
package main
import (
"fmt"
"sync"
)
var once sync.Once
func initOnce() {
fmt.Println("This is only executed once.")
}
func main() {
for i := 0; i < 5; i++ {
go once.Do(initOnce) // 保证 initOnce 只会执行一次
}
// 等待 goroutine 完成
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
once.Do(initOnce) // 只会执行一次
}()
wg.Wait()
}
once.Do(fn)
:确保函数 fn
仅执行一次。sync.Cond
(条件变量)Cond
类型提供了与其他 goroutine 协调的方式,类似于传统编程中的条件变量。它允许一个 goroutine 等待某个条件满足,然后通知其他 goroutine。
package main
import (
"fmt"
"sync"
)
var mu sync.Mutex
var cond = sync.NewCond(&mu)
var ready bool
func waiter() {
cond.L.Lock() // 上锁
defer cond.L.Unlock()
for !ready { // 检查条件
cond.Wait() // 等待条件
}
fmt.Println("The condition is ready!")
}
func signaler() {
cond.L.Lock()
ready = true // 更新条件
cond.Signal() // 唤醒一个等待的 goroutine
cond.L.Unlock()
}
func main() {
go waiter()
go signaler()
// 等待 goroutine 完成
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
signaler()
}()
wg.Wait()
}
cond.L.Lock()
:对条件变量所在的锁加锁。cond.Wait()
:阻塞当前 goroutine,直到收到通知。cond.Signal()
:通知一个等待中的 goroutine。cond.Broadcast()
:通知所有等待中的 goroutine。Go 的 sync
包提供了多种同步机制,适用于不同的并发控制需求:
sync.Mutex
:最常用的互斥锁,保证同一时刻只有一个 goroutine 访问共享资源。sync.RWMutex
:读写锁,支持多个 goroutine 同时读取,只有一个 goroutine 写入。sync.WaitGroup
:等待多个 goroutine 执行完毕。sync.Once
:确保某个操作只执行一次。sync.Cond
:条件变量,用于在多个 goroutine 间进行协调和通信。根据你的并发需求,选择合适的同步工具可以帮助你写出高效、安全的并发程序。