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

GO语言中的悲观锁与乐观锁

乐观锁和悲观锁是两种不同的并发控制策略,它们的主要区别在于对资源冲突的处理方式不同。每种锁都有适用的场景,根据实际情况选择使用哪种锁,可以帮助提高系统的并发性能和效率。

1. 悲观锁(Pessimistic Locking)

悲观锁的核心思想是“总是假设会发生冲突”,因此每次访问共享资源时都会主动加锁。这样,其他线程或者协程在获取锁之前会被阻塞,直到锁被释放。

特点:
  • 加锁:在操作数据之前,线程总是先加锁。这样可以确保在执行操作期间,其他线程无法访问该资源。
  • 阻塞:如果一个线程已经持有锁,其他线程需要等待,直到锁被释放才能继续执行。
  • 适用场景:适用于高竞争场景,数据冲突频繁发生时(如银行账户系统的资金操作),悲观锁可以避免并发问题。
示例(悲观锁):

假设我们使用数据库中的悲观锁来实现这一机制:

SELECT * FROM account WHERE account_id = 1 FOR UPDATE;

上面这条SQL语句会锁定查询到的那一行,直到事务完成才会释放锁。

Go中的悲观锁(Mutex)示例:
package main

import (
    "fmt"
    "sync"
)

var mu sync.Mutex

var counter int

func increment() {
    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()
            increment()
        }()
    }
    wg.Wait()
    fmt.Println("Counter:", counter)
}

2. 乐观锁(Optimistic Locking)

乐观锁的核心思想是“假设不会发生冲突”,因此在访问共享资源时不加锁,而是在提交数据时进行验证。具体来说,乐观锁通常通过版本号或时间戳来检测数据是否发生了变化,如果数据发生变化(即冲突),则会回滚操作或重试。

特点:
  • 无锁操作:在操作数据时不加锁,认为并发冲突的几率较低,因此可以提高系统的性能。
  • 冲突检测:当线程或事务尝试提交修改时,检查资源是否被其他线程修改过。如果没有修改,提交操作;如果已修改,则回滚或重试。
  • 适用场景:适用于冲突较少的情况,例如在线商城的商品浏览,多个用户同时查看商品信息时冲突的概率很低,可以使用乐观锁。
乐观锁的实现方式:
  1. 版本号控制:每次修改数据时,都会更新版本号,提交时通过版本号检查是否发生冲突。
  2. 时间戳控制:每次修改数据时,都会更新数据的时间戳,提交时通过时间戳检查数据是否已经被修改。
Go中的乐观锁(版本号)示例:
package main

import (
    "fmt"
    "sync"
)

type Account struct {
    balance int
    version int
    mu      sync.Mutex
}

func (a *Account) Deposit(amount int) bool {
    a.mu.Lock()
    defer a.mu.Unlock()

    currentVersion := a.version
    a.balance += amount
    a.version++  // 增加版本号

    // 假设提交操作时,检查版本号是否一致
    return currentVersion == a.version-1
}

func main() {
    acc := &Account{balance: 1000, version: 0}

    var wg sync.WaitGroup
    for 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. 悲观锁与乐观锁的选择

选择依据
  • 资源冲突的概率:如果多个线程或事务频繁修改同一资源,使用悲观锁较为合适,因为悲观锁可以防止并发写冲突。反之,如果冲突的概率较低,可以使用乐观锁来提高效率。
  • 性能要求:悲观锁由于加锁和阻塞操作,可能会导致性能下降。而乐观锁在没有冲突时无需加锁,能够提供更高的并发性能。
  • 系统的复杂度:乐观锁的实现通常比悲观锁复杂,因为它涉及冲突检测和回滚机制,而悲观锁相对简单。
总结
  • 悲观锁:适用于冲突较多的高竞争场景,确保数据安全性,但可能会影响系统性能。
  • 乐观锁:适用于冲突较少的低竞争场景,可以提供较高的并发性能,但需要额外的冲突检测和回滚机制。

http://www.kler.cn/a/550848.html

相关文章:

  • Python排序算法详解
  • 坐井说天阔---DeepSeek-R1
  • 设计模式2:单例模式
  • Docker 容器日志与监控
  • 【对比】Pandas 和 Polars 的区别
  • Unity截取RenderTexture某帧画面显示在Image上
  • Leetcode 算法题 88. 合并两个有序数组
  • 802.3 两种格式
  • Redis 10章——集群(cluster)
  • 服务器A到服务器B免密登录
  • 【拒绝算法PUA】LeetCode 1287. 有序数组中出现次数超过25%的元素
  • maven使用默认settings.xml配置时,Idea基于pom.xml更新依赖时报错,有些组件下载时连接超时
  • 解锁JavaScript新姿势:Set数据结构深度解析
  • Unity Shader示例 6: 卡渲基础 - 描边 + 着色
  • 【学术投稿-第四届智能电网和绿色能源国际学术会议(ICSGGE 2025)】CSS基本选择器详解:掌握基础,轻松布局网页
  • 深入剖析 Python 爬虫:淘宝商品详情数据抓取
  • 什么是RDD以及它在Spark中的作用
  • 地基Spring中bean生命周期和设计模式
  • 为AI聊天工具添加一个知识系统 之108 详细设计之49 相提并论的三者、三位一体Triad和圣灵倒三角
  • Java爬虫获取1688商品搜索API接口的实现指南