悲观锁和乐观锁
悲观锁
核心思想
假设并发冲突一定会发生,因此在访问共享资源的时候,先加锁,确保操作期间数据不会被其它线程修改。
特点
- 提前枷锁: 操作前获取锁,操作后释放锁
- 阻塞等待: 如果锁被占用,其它线程会阻塞或者自旋等待,直到锁释放
- 强一致性: 保证操作期间数据的独占性,适合写多少读多少的场景
典型实现
互斥锁:go的sync.Mutex
数据库行锁: 如SELECT … FOR UPDATE
使用场景
- 写操作频繁,冲突概率高
- 需要严格保证数据一致性。
乐观锁
核心思想
假设冲突很少发生,因此直接操作数据,仅在提交时检测是否发生冲突。如果冲突则重试或者放弃。
特点
- 无锁操作: 操作期间不加锁,仅在提交时验证数据版本
- 非阻塞: 线程不会互相阻塞,适合 读多写少的场景
- 依赖版本机制: 通过版本号或者时间戳检测数据变更
典型实现
Go的atomic.CompareAndSwap
数据库版本控制,通过version字段实现。
适用场景
- 读操作远远多于写操作
- 冲突概率低,而且重试成本可控
对比总结
特性 | 悲观锁 | 乐观锁 |
---|---|---|
冲突假设 | 认为一定会发生冲突 | 认为冲突很少发生 |
锁机制 | 操作前加锁 | 无锁,提交时检测冲突 |
性能 | 高并发写时性能差 | 高并发读时性能高 |
数据一致性 | 强一致性 | 最终一致性 |
实现复杂度 | 简单 | 需要处理冲突(如重试逻辑) |
典型应用 | 数据库行锁、转账操作 | 计数器、库存系统、无锁数据结构 |