V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
SlowDown
V2EX  ›  程序员

锁续期和锁归属问题?

  •  
  •   SlowDown · 14 天前 · 1096 次点击

    各位佬们,最近在看八股文,有个地方不是很理解,用 Redis 做分布式锁的锁续期和锁归属问题

    是这样一个场景

    客户端 A 拿到锁并设置了过期时间,但是执行时间太长,导致还没执行完锁就过期了,然后锁被客户端 B 拿到了,此时客户端 A 执行结束然后释放锁的时候释放的其实是客户端 B 的锁

    网上的文章说

    解决锁续期问题可以在拿到锁之后起一个守护线程做锁续期

    解决锁归属问题可以给分布式锁的 value 设置一个 UUID ,然后客户端释放锁的时候先看是不是自己的锁,如果是再释锁

    那么问题来了,在有守护线程做锁续期的情况下,是否还存在锁归属问题?

    毕竟有客户端 A 有守护线程之后,锁不会再因为过期而被客户端 B 拿到了。

    17 条回复    2024-05-16 17:20:50 +08:00
    laikick
        1
    laikick  
       14 天前
    守护线程做锁续期 + 唯一标识符 几乎不存在锁归属问题了. 可能是我没遇到过
    SlowDown
        2
    SlowDown  
    OP
       14 天前
    @laikick 嗯,确实
    不过我的意思是只用守护线程做锁续期能不能同时解决锁归属的问题,也就是说,在有守护线程的情况下,是否还需要这个 UUID
    毕竟有守护线程之后,锁不会再因为过期而被其他客户端拿到了
    imzhoukunqiang
        3
    imzhoukunqiang  
       14 天前 via Android
    有考虑过重入吗?不放线程 id 怎么重入?
    SlowDown
        4
    SlowDown  
    OP
       14 天前
    @imzhoukunqiang 考虑重入那肯定得需要标识当前线程
    就不考虑重入的情况下怎么说,我看网上的文章在谈及这个唯一 id 的时候,都没提到重入这两个字
    imzhoukunqiang
        5
    imzhoukunqiang  
       14 天前 via Android
    看一下开源的实现,不要看网上的"面经",随便问一下就露馅了
    imzhoukunqiang
        6
    imzhoukunqiang  
       14 天前 via Android
    顺便我也问几个问题,redis 阻塞锁怎么实现?怎么唤醒被阻塞的线程?
    SlowDown
        7
    SlowDown  
    OP
       13 天前
    @imzhoukunqiang 我这小菜鸡,还没参加工作,这个确实不清楚
    不过我的思路是,线程获取锁失败后将自己加入队列中并阻塞,由另外一个线程定期检测锁是否可用,检测到可用之后从队列中唤醒线程
    不过我感觉我这个有问题,一个是这个定期时间不好确定,是一个魔数,另外一个就是唤醒之后的线程可能还是会获取不到锁,再被阻塞,性能有问题
    SlowDown
        8
    SlowDown  
    OP
       13 天前
    @imzhoukunqiang 另外,可以请教一下大哥这个问题该怎么解吗?(爆爆金币)
    imzhoukunqiang
        9
    imzhoukunqiang  
       13 天前
    @SlowDown 用 redis 的 hash 结构,key 为锁的 ID ,field 为线程 id ,value 为数字,value 用来记录重入次数。

    阻塞锁的唤醒可以通过 redis 的 sub/pub 进行通知唤醒。
    fkdtz
        10
    fkdtz  
       13 天前   ❤️ 1
    虽然有守护线程持续的续约锁,但还是有必要在释放锁时做校验的,关键在于「你不能假设系统任何时刻都能正常运行」
    假如锁续期的守护进程出现异常而没有正常执行续期,那么直接释放锁而不做校验就会出现锁被误删除的情况。

    其实你有没有想过,既然锁续期线程一直在给锁延长有效期,那不就相当于是一把永久有效的锁吗?那直接用一个不加有效期的永久锁不就好了,为什么还要弄个有效期再加一个续期线程?而且如果用一个永久有效的锁甚至连锁误删除的问题也不会存在了。

    选择用带有效期的锁的原因跟前面的问题有同样的考虑:「你不能假设系统任何时刻都能正常运行」,如果主线程加锁后挂掉,那么不加有效期的锁就会一直存在,所以必须要带上有效期,带上有效期就引出了锁归属和锁续期的概念,进而才引出后面的各种方案。
    SlowDown
        11
    SlowDown  
    OP
       13 天前
    @fkdtz 「你不能假设系统任何时刻都能正常运行」,这个确实
    「如果主线程加锁后挂掉,那么不加有效期的锁就会一直存在」,这个我有点疑问
    我们一般加锁的时候都会在 finally 语句里面写解锁的,线程出现异常会进入 finally 块解锁,所以锁不会一直续期
    如果说挂到连 finally 块都不会执行的话,那这个时候 main 线程已经挂了,虚拟机里面不存在非守护线程了,守护线程也会被销毁
    waytodelay
        12
    waytodelay  
       13 天前
    看下 redission 的实现
    fkdtz
        13
    fkdtz  
       13 天前
    @SlowDown
    这里强调的是锁带上有效期的必要性,即如果用一个永久有效的锁并且不使用续期线程会有什么问题。
    另外我没看懂你的疑问是什么?
    SlowDown
        14
    SlowDown  
    OP
       13 天前
    @fkdtz
    「如果用一个永久有效的锁并且不使用守护线程续期」,那么拿到锁的线程挂掉之后,锁不会被释放,别的线程也拿不到锁,就造成了死锁。
    我的疑问是,在使用守护线程续期的情况下,「如果主线程加锁后挂掉,那么不加有效期的锁就会一直存在」这种状况是否会发生?
    「如果主线程加锁后挂掉」那么有两种情况
    一、这个线程出现了异常,这个可以用 try...catch...finally 来处理,即在 finally 块中解锁,这样不论是否发生异常,锁都会被释放
    二、这个线程挂得非常严重,连 finally 块都来不及执行就彻底挂了,那么这种情况下,包括 main() 在内的虚拟机里面的所有非守护线程肯定也已经挂掉了,此时守护线程也会被销毁,不再继续执行锁续期,锁在过期之后自动释放
    所以,我感觉「不加有效期的锁就会一直存在」是不会的。
    fkdtz
        15
    fkdtz  
       13 天前
    都永久有效的锁了,还怎么自动释放?
    你要不再好好看看
    SlowDown
        16
    SlowDown  
    OP
       13 天前
    @fkdtz 哦,懂了懂了,我有点过度解读导致理解偏差了,谢谢大哥
    123zouwen
        17
    123zouwen  
       13 天前
    直接看 redisson 的 wiki 和对应的源码, 可以解答你的疑问
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3422 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 32ms · UTC 10:38 · PVG 18:38 · LAX 03:38 · JFK 06:38
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.