当前位置: 首页 > news >正文

【Go】--互斥锁和读写锁

【Go】–互斥锁和读写锁

作用

互斥锁(Mutex)和读写锁(RWMutex)是Go语言中用于保护共享资源、防止数据竞争的重要同步机制。

1. 数据竞争问题

1.1 数据竞争

数据竞争(Data Race)发生在多个goroutine并发访问同一共享变量,且至少有一个访问是写入操作时。如果没有适当的同步机制,会导致不可预测的结果。

1.2 数据竞争的危害

  • 结果不确定性:相同的代码可能产生不同的结果
  • 内存损坏:可能导致程序崩溃或数据损坏
  • 难以调试:问题可能只在特定条件下出现

2. 互斥锁(Mutex)

2.1 互斥锁

互斥锁(Mutual Exclusion Lock)是最基本的同步原语,保证同一时间只有一个goroutine可以访问受保护的临界区。

2.2 互斥锁操作

var mutex sync.Mutex// 加锁
mutex.Lock()// 临界区代码
// ...// 解锁
mutex.Unlock()

3. 代码示例分析

3.1 数据竞争演示

package test_lockimport ("fmt""math/rand""sync""time"
)var wait sync.WaitGroup
var count1 = 0func 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 + ansfmt.Println("goroutine", i, "count1:", *data)wait.Done()}(&count1)}wait.Wait()fmt.Println("最终结果", count1)
}

核心要点:

  1. 无锁并发访问

    var count1 = 0go func(data *int) {temp := *data           // 读取共享变量// ... 计算耗时*data = temp + ans      // 写入共享变量
    }(&count1)
    
  2. 数据竞争特征

    • 多个goroutine同时读写count1变量
    • 读-改-写操作不是原子的
    • 最终结果通常小于预期值

关键:

  • 典型的数据竞争
  • 由于goroutine执行顺序不确定,结果不可预测
  • 需要同步机制来保证正确性

3.2 互斥锁解决方案

package test_lockimport ("fmt""math/rand""sync""time"
)var wg sync.WaitGroup
var count = 0// 声明一个互斥锁
var mutex sync.Mutexfunc 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)
}

核心要点:

  1. 互斥锁保护临界区

    var mutex sync.Mutexgo func(data *int) {// 加锁mutex.Lock()temp := *data// ... 计算耗时*data = temp + ans// 解锁mutex.Unlock()
    }(&count)
    
  2. 锁的正确使用

    • 在访问共享资源前加锁
    • 在操作完成后立即解锁
    • 使用defer确保解锁被执行

关键:

  • 互斥锁保证了操作的原子性
  • 最终结果总是正确的(10个goroutine,每个+1,结果应为10)
  • 性能会有一定损耗,但保证了正确性

3.3 读写锁(RWMutex)

package test_lockimport ("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()
}

核心要点:

  1. 读写锁操作

    var rw sync.RWMutex// 写锁操作
    func write(count int) {rw.Lock()           // 加写锁defer rw.Unlock()   // 解读锁// 写操作...
    }// 读锁操作  
    func read(count int) {rw.RLock()          // 加读锁defer rw.RUnlock()  // 解读锁// 读操作...
    }
    
  2. 读写锁特性

    • 读锁:多个goroutine可以同时持有读锁
    • 写锁:写锁是排他的,持有写锁时不能有读锁或其他写锁
    • 升级规则:读锁不能升级为写锁

关键:

  • 读写锁适用于"读多写少"的场景
  • 提高了并发读取的性能
  • 写操作仍然需要独占访问

4. 锁的类型对比

4.1 互斥锁 vs 读写锁

特性互斥锁(Mutex)读写锁(RWMutex)
并发读取不支持支持多个goroutine同时读取
写入操作完全互斥完全互斥
性能开销较低较高(需要维护读锁计数)
适用场景读写频率相当读多写少

4.2 选择原则

使用互斥锁的情况:

  • 读写操作频率相当
  • 临界区代码执行时间短
  • 代码逻辑简单

使用读写锁的情况:

  • 读操作远多于写操作
  • 读操作耗时较长
  • 需要最大化读取并发性

5. 锁的使用场景

5.1 共享数据保护

// 保护共享map
var dataMap = make(map[string]string)
var mapMutex sync.RWMutexfunc 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
}

5.2 计数器保护

// 原子计数器
var counter int
var counterMutex sync.Mutexfunc Increment() {counterMutex.Lock()defer counterMutex.Unlock()counter++
}func GetCount() int {counterMutex.Lock()defer counterMutex.Unlock()return counter
}

5.3 资源池管理

// 连接池管理
type ConnectionPool struct {pool []*Connectionmutex 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
}

6. 指南

6.1 锁的使用

  1. 保持锁的粒度适中

    // 错误:锁的粒度过大
    mutex.Lock()
    // 大量非临界区代码...
    // 共享资源访问
    mutex.Unlock()// 正确:只保护必要的临界区
    // 非临界区代码...
    mutex.Lock()
    // 共享资源访问
    mutex.Unlock()
    // 非临界区代码...
    
  2. 使用defer确保解锁

    func safeOperation() {mutex.Lock()defer mutex.Unlock()  // 确保在任何情况下都会解锁// 可能panic的代码if err != nil {panic("error")}
    }
    

6.2 避免死锁

  1. 锁的顺序一致性

    // 错误:可能产生死锁
    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锁// ...
    }
    
  2. 使用超时机制

    // 使用context实现超时
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()select {
    case <-acquireLock(ctx, mutex):// 成功获取锁
    case <-ctx.Done():// 超时处理
    }
    

6.3 性能优化

  1. 减少锁竞争

    • 使用更细粒度的锁
    • 考虑使用无锁数据结构
    • 使用本地缓存减少锁的使用频率
  2. 读写分离

    // 使用读写锁优化读多写少的场景
    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
    }
    

7. 高级主题

7.1 条件变量(Cond)

条件变量用于在特定条件下等待或通知goroutine:

var mutex sync.Mutex
cond := sync.NewCond(&mutex)// 等待条件
func waitForCondition() {mutex.Lock()for !condition {cond.Wait()  // 释放锁并等待}// 条件满足,执行操作mutex.Unlock()
}// 通知条件满足
func signalCondition() {mutex.Lock()condition = truecond.Signal()  // 通知一个等待的goroutinemutex.Unlock()
}

7.2 原子操作

对于简单的计数器操作,可以使用原子操作避免锁的开销:

import "sync/atomic"var counter int64func Increment() {atomic.AddInt64(&counter, 1)
}func GetCount() int64 {return atomic.LoadInt64(&counter)
}

8. 调试和测试

8.1 数据竞争检测

使用Go内置的竞争检测器:

# 编译时启用竞争检测
go build -race main.go# 运行程序
go run -race main.go

8.2 性能分析

使用pprof分析锁竞争:

import _ "net/http/pprof"// 在程序中启动pprof服务器
go func() {log.Println(http.ListenAndServe("localhost:6060", nil))
}()
http://www.dtcms.com/a/565332.html

相关文章:

  • 《从适配器本质到面试题:一文掌握 C++ 栈、队列与优先级队列核心》
  • 心理咨询网站模板做网站手机
  • 光学3D表面轮廓仪中Rz代表什么?如何精准测量Rz?
  • ps做登录网站北京网站制作工作室
  • git rebase提交
  • vue3引入icon-font
  • 基于开源操作系统搭建K8S高可用集群
  • 学做网站论坛 可以吗做网站是不是太麻烦了
  • leetcode 1578 使绳子变成彩色的最短时间
  • 中国建设银行网上银行官方网站长沙优秀网站建设
  • 1.7 Foundry介绍
  • 什么是向量数据库?主流产品介绍与实战演练
  • redission实现延时队列
  • 浏览器端缓存地图请求:使用 IndexedDB + ajax-hook 提升地图加载速度
  • 地铁工程建设论文投稿网站谷歌广告代运营
  • 广东备案网站软件开发怎么学
  • 【成长纪实】鸿蒙 ArkTS 语言从零到一完整指南
  • PyTorch模型部署实战:从TorchScript到LibTorch的完整路径
  • 网站开发后台结构江西建设职业技术学院网站
  • 如何导出VSCode的已安装扩展列表?
  • 高级系统架构师笔记——系统质量属性与架构评估(1)软件系统质量属性
  • Vscode参数设置及使用记录ubuntu2204(更新中)
  • Linux上vscode c/c++开发环境搭建详细-abuild
  • vscode多文件编程bug记录
  • 分布式答案解析
  • 做耳机套的网站常用网站推广方法的适用性
  • 网站建设增长率呼和浩特建设厅网站
  • AI 音乐工具 Suno 和 Producer 对比
  • KeilIDE背后的命令
  • flash中文网站模板带有flash的网站