在并发编程中,当多个goroutine同时访问共享资源时,可能会发生数据竞态(data race)。互斥锁(mutual exclusion lock)是保护共享资源最常用的同步原语。Go语言的sync.Mutex提供了简单的Lock()和Unlock()方法,保证同一时刻只有一个goroutine可以执行被保护的代码段。此外,sync.RWMutex实现了读写锁,适用于读多写少的场景。
多个goroutine不加保护地修改共享变量会导致结果不正确。
var counter int
var wg sync.WaitGroup
func increment() {
defer wg.Done()
for i := 0; i < 1000; i++ {
counter++ // 非原子操作,存在竞态
}
}
func main() {
for i := 0; i < 10; i++ {
wg.Add(1)
go increment()
}
wg.Wait()
fmt.Println("最终计数:", counter) // 通常小于10000,且每次结果不同
}
go run -race main.go可以检测数据竞态。Lock()获取锁,Unlock()释放锁。通常配合defer确保锁被释放。
import "sync"
var mu sync.Mutex
var counter int
func incrementSafe() {
for i := 0; i < 1000; i++ {
mu.Lock()
counter++
mu.Unlock()
}
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
incrementSafe()
}()
}
wg.Wait()
fmt.Println("最终计数:", counter) // 总是10000
}
sync.RWMutex区分读锁和写锁:读锁可以并发持有,写锁独占。适用于读多写少的场景。
type SafeMap struct {
mu sync.RWMutex
data map[string]int
}
func (m *SafeMap) Get(key string) int {
m.mu.RLock() // 读锁
defer m.mu.RUnlock()
return m.data[key]
}
func (m *SafeMap) Set(key string, value int) {
m.mu.Lock() // 写锁
defer m.mu.Unlock()
m.data[key] = value
}
func (m *SafeMap) Delete(key string) {
m.mu.Lock()
defer m.mu.Unlock()
delete(m.data, key)
}
读锁之间不互斥,可提高并发读的性能。
死锁常见情况:同一goroutine重复加锁、多个锁互相等待。
// 错误:在解锁前再次加锁(单线程死锁)
mu.Lock()
mu.Lock() // 死锁!同一个goroutine不能再次获取已持有的锁
// 正确做法
mu.Lock()
// ... 操作
mu.Unlock()
// 如果需要再次加锁,在解锁之后
避免死锁的方法:按相同顺序获取锁,使用defer确保解锁,避免嵌套锁复杂度过高。
对于整型、指针等简单类型,可以使用sync/atomic包进行无锁原子操作,性能优于互斥锁。
import "sync/atomic"
var counter int64
func incrementAtomic() {
atomic.AddInt64(&counter, 1)
}
func main() {
// 多个goroutine调用 incrementAtomic 安全,无需互斥锁
}
sync.Mutex和sync.RWMutex不允许值拷贝,否则会导致锁失效或死锁。应使用指针传递或封装在结构体中。
// 错误:值传递会复制锁
func (m MyMutex) Lock() { // 接收者为值,锁被复制
m.Mutex.Lock()
}
// 正确:使用指针接收者
func (m *MyMutex) Lock() {
m.Mutex.Lock()
}
Go官方建议:当共享资源需要被多个goroutine访问并保护时,使用互斥锁;当需要在goroutine之间传递数据所有权或协调行为时,使用通道。两者并非互斥,有时可以结合使用。
package main
import (
"fmt"
"sync"
)
type Account struct {
balance int
mu sync.Mutex
}
func (a *Account) Deposit(amount int) {
a.mu.Lock()
defer a.mu.Unlock()
a.balance += amount
}
func (a *Account) Withdraw(amount int) bool {
a.mu.Lock()
defer a.mu.Unlock()
if a.balance < amount {
return false
}
a.balance -= amount
return true
}
func (a *Account) Balance() int {
a.mu.Lock()
defer a.mu.Unlock()
return a.balance
}
// 转账:避免死锁,按账户ID顺序加锁
func Transfer(from, to *Account, amount int) {
// 简单起见,假设这里使用锁顺序:按地址大小排序
if from == to {
return
}
// 按地址顺序加锁,避免死锁
var first, second *sync.Mutex
if from < to {
first, second = &from.mu, &to.mu
} else {
first, second = &to.mu, &from.mu
}
first.Lock()
second.Lock()
defer first.Unlock()
defer second.Unlock()
if from.balance >= amount {
from.balance -= amount
to.balance += amount
}
}
func main() {
alice := &Account{balance: 1000}
bob := &Account{balance: 500}
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
Transfer(alice, bob, 100)
}()
}
wg.Wait()
fmt.Printf("Alice余额: %d, Bob余额: %d\n", alice.Balance(), bob.Balance())
}