乐观锁和悲观锁是两种不同的并发控制策略,它们的主要区别在于对资源冲突的处理方式不同。每种锁都有适用的场景,根据实际情况选择使用哪种锁,可以帮助提高系统的并发性能和效率。
1. 悲观锁(Pessimistic Locking)
悲观锁的核心思想是“总是假设会发生冲突”,因此每次访问共享资源时都会主动加锁。这样,其他线程或者协程在获取锁之前会被阻塞,直到锁被释放。
特点:
- 加锁:在操作数据之前,线程总是先加锁。这样可以确保在执行操作期间,其他线程无法访问该资源。
- 阻塞:如果一个线程已经持有锁,其他线程需要等待,直到锁被释放才能继续执行。
- 适用场景:适用于高竞争场景,数据冲突频繁发生时(如银行账户系统的资金操作),悲观锁可以避免并发问题。
示例(悲观锁):
假设我们使用数据库中的悲观锁来实现这一机制:
SELECT * FROM account WHERE account_id = 1 FOR UPDATE;
上面这条SQL语句会锁定查询到的那一行,直到事务完成才会释放锁。
Go中的悲观锁(Mutex)示例:
package mainimport ("fmt""sync"
)var mu sync.Mutexvar counter intfunc increment() {mu.Lock() // 获取锁counter++mu.Unlock() // 释放锁
}func main() {var wg sync.WaitGroupfor i := 0; i < 10; i++ {wg.Add(1)go func() {defer wg.Done()increment()}()}wg.Wait()fmt.Println("Counter:", counter)
}
2. 乐观锁(Optimistic Locking)
乐观锁的核心思想是“假设不会发生冲突”,因此在访问共享资源时不加锁,而是在提交数据时进行验证。具体来说,乐观锁通常通过版本号或时间戳来检测数据是否发生了变化,如果数据发生变化(即冲突),则会回滚操作或重试。
特点:
- 无锁操作:在操作数据时不加锁,认为并发冲突的几率较低,因此可以提高系统的性能。
- 冲突检测:当线程或事务尝试提交修改时,检查资源是否被其他线程修改过。如果没有修改,提交操作;如果已修改,则回滚或重试。
- 适用场景:适用于冲突较少的情况,例如在线商城的商品浏览,多个用户同时查看商品信息时冲突的概率很低,可以使用乐观锁。
乐观锁的实现方式:
- 版本号控制:每次修改数据时,都会更新版本号,提交时通过版本号检查是否发生冲突。
- 时间戳控制:每次修改数据时,都会更新数据的时间戳,提交时通过时间戳检查数据是否已经被修改。
Go中的乐观锁(版本号)示例:
package mainimport ("fmt""sync"
)type Account struct {balance intversion intmu sync.Mutex
}func (a *Account) Deposit(amount int) bool {a.mu.Lock()defer a.mu.Unlock()currentVersion := a.versiona.balance += amounta.version++ // 增加版本号// 假设提交操作时,检查版本号是否一致return currentVersion == a.version-1
}func main() {acc := &Account{balance: 1000, version: 0}var wg sync.WaitGroupfor i := 0; i < 10; i++ {wg.Add(1)go func() {defer wg.Done()success := acc.Deposit(100)if success {fmt.Println("Deposit successful!")} else {fmt.Println("Version conflict occurred!")}}()}wg.Wait()fmt.Println("Final balance:", acc.balance)
}
3. 悲观锁与乐观锁的选择
选择依据:
- 资源冲突的概率:如果多个线程或事务频繁修改同一资源,使用悲观锁较为合适,因为悲观锁可以防止并发写冲突。反之,如果冲突的概率较低,可以使用乐观锁来提高效率。
- 性能要求:悲观锁由于加锁和阻塞操作,可能会导致性能下降。而乐观锁在没有冲突时无需加锁,能够提供更高的并发性能。
- 系统的复杂度:乐观锁的实现通常比悲观锁复杂,因为它涉及冲突检测和回滚机制,而悲观锁相对简单。
总结:
- 悲观锁:适用于冲突较多的高竞争场景,确保数据安全性,但可能会影响系统性能。
- 乐观锁:适用于冲突较少的低竞争场景,可以提供较高的并发性能,但需要额外的冲突检测和回滚机制。