Go语言并发控制:sync.Mutex、sync.RWMutex和sync.WaitGroup详解
在Go语言中,编写并发程序是家常便饭。Go提供了多种并发原语来帮助我们管理并发,其中sync.Mutex
、sync.RWMutex
和sync.WaitGroup
是三个非常常用的同步原语。本文将详细介绍这三个同步原语,并提供示例代码帮助理解它们的使用。
sync.Mutex
sync.Mutex
是一个互斥锁,用于保护临界区,确保同一时间只有一个goroutine可以访问共享资源。互斥锁有两种状态:锁定和解锁。当一个goroutine获取(锁定)互斥锁时,其他尝试获取该锁的goroutines将被阻塞,直到锁被释放(解锁)。
示例代码
package main
import (
"fmt"
"sync"
)
func main() {
var mutex sync.Mutex
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
mutex.Lock()
fmt.Println("goroutine 1 is accessing shared resource")
mutex.Unlock()
}()
go func() {
defer wg.Done()
mutex.Lock()
fmt.Println("goroutine 2 is accessing shared resource")
mutex.Unlock()
}()
wg.Wait()
}
在这个例子中,两个goroutines尝试访问共享资源,但是由于互斥锁的存在,它们不会同时访问。
sync.RWMutex
sync.RWMutex
是一个读写互斥锁,它允许多个goroutines同时读取共享资源,但是在写入时会锁定互斥锁,确保没有其他goroutine正在读取或写入。这在读取操作远多于写入操作的场景中非常有用,因为它可以提高并发性能。
示例代码
package main
import (
"fmt"
"sync"
)
var (
sum int
mutex sync.RWMutex
)
func main() {
var wg sync.WaitGroup
wg.Add(110)
for i := 1; i <= 100; i++ {
go func() {
defer wg.Done()
add(10)
}()
}
for i := 1; i <= 10; i++ {
go func() {
defer wg.Done()
fmt.Println("sum的值:", readSum())
}()
}
wg.Wait()
fmt.Println("sum:", sum)
}
func add(num int) {
mutex.Lock()
defer mutex.Unlock()
sum += num
}
func readSum() int {
mutex.RLock()
defer mutex.RUnlock()
b := sum
return b
}
在这个例子中,add
函数修改共享资源sum
,而readSum
函数读取sum
。由于readSum
被频繁调用,使用sync.RWMutex
可以提高程序的并发性能。
sync.WaitGroup
sync.WaitGroup
用于等待一组goroutines完成。它计数goroutines的数量,并在所有goroutines完成它们的工作后通知主goroutine。
示例代码
package main
import (
"fmt"
"sync"
"time"
)
func main() {
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
time.Sleep(time.Second)
fmt.Printf("goroutine %d finished\n", i)
}(i)
}
wg.Wait()
fmt.Println("所有goroutines已完成")
}
在这个例子中,我们启动了5个goroutines,每个goroutine在完成它的工作后调用wg.Done()
来减少WaitGroup
的计数。主goroutine调用wg.Wait()
来等待所有goroutines完成。
总结
sync.Mutex
、sync.RWMutex
和sync.WaitGroup
是Go语言中非常重要的同步原语,它们帮助我们安全地管理并发,避免竞态条件和死锁。通过合理使用这些同步原语,我们可以编写出高效且安全的并发程序。