Go语言中,互斥锁与读写锁作为核心同步工具,能有效防止并发访问共享资源时的数据竞争问题,保障程序正确性。下面将详细介绍它们的具体用法与差异。

数据竞争(Data Race)发生在多个goroutine并发访问同一共享变量,且至少有一个访问是写入操作时。如果没有适当的同步机制,会导致不可预测的结果。
互斥锁(Mutual Exclusion Lock)是最基本的同步原语,保证同一时间只有一个goroutine可以访问受保护的临界区。
var mutex sync.Mutex // 加锁 mutex.Lock() // 临界区代码 // ... // 解锁 mutex.Unlock()
package test_lock
import (
"fmt"
"math/rand"
"sync"
"time"
)
var wait sync.WaitGroup
var count1 = 0
func Demo01() {
wait.Add(10)
for i := 0; i < 10; i++ {
go func(data *int) {
// 模拟访问耗时
time.Sleep(time.Millisecond * time.Duration(rand.Intn(1000)))
// 访问数据
temp := *data
// 模拟计算耗时
// 拉大时间,让数据竞争更严重(更好观察结果)
time.Sleep(time.Millisecond * time.Duration(rand.Intn(1000)))
ans := 1
// 修改数据
*data = temp + ans
fmt.Println("goroutine", i, "count1:", *data)
wait.Done()
}(&count1)
}
wait.Wait()
fmt.Println("最终结果", count1)
}
核心要点:
无锁并发访问:
var count1 = 0
go func(data *int) {
temp := *data // 读取共享变量
// ... 计算耗时
*data = temp + ans // 写入共享变量
}(&count1)
数据竞争特征:
count1变量关键:
package test_lock
import (
"fmt"
"math/rand"
"sync"
"time"
)
var wg sync.WaitGroup
var count = 0
// 声明一个互斥锁
var mutex sync.Mutex
func Demo02() {
wg.Add(10)
for i := 0; i < 10; i++ {
go func(data *int) {
// 加锁
mutex.Lock()
// 模拟访问耗时
time.Sleep(time.Millisecond * time.Duration(rand.Intn(1000)))
// 访问数据
temp := *data
// 模拟计算耗时
// 拉大时间,让数据竞争更严重(更好观察结果)
time.Sleep(time.Millisecond * time.Duration(rand.Intn(1000)))
ans := 1
// 修改数据
*data = temp + ans
// 解锁
mutex.Unlock()
fmt.Println("goroutine", i, "count:", *data)
wg.Done()
}(&count)
}
wg.Wait()
fmt.Println("最终结果", count)
}
核心要点:
互斥锁保护临界区:
var mutex sync.Mutex
go func(data *int) {
// 加锁
mutex.Lock()
temp := *data
// ... 计算耗时
*data = temp + ans
// 解锁
mutex.Unlock()
}(&count)
锁的正确使用:
defer确保解锁被执行关键:
package test_lock
import (
"fmt"
"sync"
"time"
)
var wg3 sync.WaitGroup
var rw sync.RWMutex
// 实现可以多人读 但是只能一人写
func write(count int) {
defer wg3.Done()
rw.Lock()
defer rw.Unlock()
fmt.Println("goroutine", count, "写操作>>>>>>")
time.Sleep(time.Second * 2)
}
func read(count int) {
defer wg3.Done()
fmt.Println("goroutine", count, "<<<<<<<读操作")
time.Sleep(time.Second * 2)
}
func Demo03() {
for i := 0; i < 10; i++ {
wg3.Add(1)
go write(i)
}
for i := 0; i < 10; i++ {
wg3.Add(1)
go read(i)
}
wg3.Wait()
}
核心要点:
读写锁操作:
var rw sync.RWMutex
// 写锁操作
func write(count int) {
rw.Lock() // 加写锁
defer rw.Unlock() // 解读锁
// 写操作...
}
// 读锁操作
func read(count int) {
rw.RLock() // 加读锁
defer rw.RUnlock() // 解读锁
// 读操作...
}
读写锁特性:
关键:
| 特性 | 互斥锁(Mutex) | 读写锁(RWMutex) |
|---|---|---|
| 并发读取 | 不支持 | 支持多个goroutine同时读取 |
| 写入操作 | 完全互斥 | 完全互斥 |
| 性能开销 | 较低 | 较高(需要维护读锁计数) |
| 适用场景 | 读写频率相当 | 读多写少 |
使用互斥锁的情况:
使用读写锁的情况:
// 保护共享map
var dataMap = make(map[string]string)
var mapMutex sync.RWMutex
func GetValue(key string) string {
mapMutex.RLock()
defer mapMutex.RUnlock()
return dataMap[key]
}
func SetValue(key, value string) {
mapMutex.Lock()
defer mapMutex.Unlock()
dataMap[key] = value
}
// 原子计数器
var counter int
var counterMutex sync.Mutex
func Increment() {
counterMutex.Lock()
defer counterMutex.Unlock()
counter++
}
func GetCount() int {
counterMutex.Lock()
defer counterMutex.Unlock()
return counter
}
// 连接池管理
type ConnectionPool struct {
pool []*Connection
mutex sync.Mutex
}
func (p *ConnectionPool) Get() *Connection {
p.mutex.Lock()
defer p.mutex.Unlock()
if len(p.pool) > 0 {
conn := p.pool[0]
p.pool = p.pool[1:]
return conn
}
return nil
}
保持锁的粒度适中:
// 错误:锁的粒度过大 mutex.Lock() // 大量非临界区代码... // 共享资源访问 mutex.Unlock() // 正确:只保护必要的临界区 // 非临界区代码... mutex.Lock() // 共享资源访问 mutex.Unlock() // 非临界区代码...
使用defer确保解锁:
func safeOperation() {
mutex.Lock()
defer mutex.Unlock() // 确保在任何情况下都会解锁
// 可能panic的代码
if err != nil {
panic("error")
}
}
锁的顺序一致性:
// 错误:可能产生死锁
func operation1() {
mutexA.Lock()
mutexB.Lock()
// ...
mutexB.Unlock()
mutexA.Unlock()
}
func operation2() {
mutexB.Lock()
mutexA.Lock() // 可能死锁
// ...
}
// 正确:保持一致的锁顺序
func operation1() {
mutexA.Lock()
mutexB.Lock()
// ...
}
func operation2() {
mutexA.Lock() // 先获取A锁
mutexB.Lock() // 再获取B锁
// ...
}
使用超时机制:
// 使用context实现超时
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
select {
case <-acquireLock(ctx, mutex):
// 成功获取锁
case <-ctx.Done():
// 超时处理
}
减少锁竞争:
读写分离:
// 使用读写锁优化读多写少的场景
var rw sync.RWMutex
var data []string
// 多个goroutine可以同时读取
func ReadData() []string {
rw.RLock()
defer rw.RUnlock()
return data
}
// 写操作仍然需要互斥
func WriteData(newData []string) {
rw.Lock()
defer rw.Unlock()
data = newData
}
条件变量用于在特定条件下等待或通知goroutine:
var mutex sync.Mutex
cond := sync.NewCond(&mutex)
// 等待条件
func waitForCondition() {
mutex.Lock()
for !condition {
cond.Wait() // 释放锁并等待
}
// 条件满足,执行操作
mutex.Unlock()
}
// 通知条件满足
func signalCondition() {
mutex.Lock()
condition = true
cond.Signal() // 通知一个等待的goroutine
mutex.Unlock()
}
对于简单的计数器操作,可以使用原子操作避免锁的开销:
import "sync/atomic"
var counter int64
func Increment() {
atomic.AddInt64(&counter, 1)
}
func GetCount() int64 {
return atomic.LoadInt64(&counter)
}
使用Go内置的竞争检测器:
# 编译时启用竞争检测 go build -race main.go # 运行程序 go run -race main.go
使用pprof分析锁竞争:
import _ "net/http/pprof"
// 在程序中启动pprof服务器
go func() {
log.Println(http.ListenAndServe("localhost:6060", nil))
}()
通过合理选用互斥锁与读写锁,能够有效解决Go并发编程中的数据竞争问题,确保程序正确性与性能平衡。实际开发中需根据读写比例、临界区大小等因素选择合适锁机制,并遵循最小粒度、避免死锁等最佳实践。