文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Golang怎么实现可重入锁

2023-06-30 16:23

关注

本文小编为大家详细介绍“Golang怎么实现可重入锁”,内容详细,步骤清晰,细节处理妥当,希望这篇“Golang怎么实现可重入锁”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。

什么是可重入锁

我们平时说的分布式锁,一般指的是在不同服务器上的多个线程中,只有一个线程能抢到一个锁,从而执行一个任务。而我们使用锁就是保证一个任务只能由一个线程来完成。所以我们一般是使用这样的三段式逻辑:

Lock();
DoJob();
Unlock();

但是由于我们的系统都是分布式的,这个锁一般不会只放在某个进程中,我们会借用第三方存储,比如 Redis 来做这种分布式锁。但是一旦借助了第三方存储,我们就必须面对这个问题:Unlock是否能保证一定运行呢?

这个问题,我们面对的除了程序的bug之外,还有网络的不稳定,进程被杀死,服务器被down机等。我们是无法保证Unlock一定被运行的。

那么我们就一般在Lock的时候为这个锁加一个超时时间作为兜底。

LockByExpire(duration);
DoJob();
Unlock();

这个超时时间是为了一旦出现异常情况导致Unlock没有被运行,这个锁在duration时间内也会被自动释放。这个在redis中我们一般就是使用set ex 来进行锁超时的设定。

但是有这个超时时间我们又遇上了问题,超时时间设置多久合适呢?当然要设置的比 DoJob 消耗的时间更长,否则的话,在任务还没结束的时候,锁就被释放了,还是有可能导致并发任务的存在。

但是实际上,同样由于网络超时问题,系统运行状况问题等,我们是无法准确知道DoJob这个函数要执行多久的。那么这时候怎么办呢?

有两个办法:

第一个方法,我们可以对DoJob做一个超时设置。让DoJob最多只能执行n秒,那么我的分布式锁的超时时长设置比n秒长就可以了。为一个任务设置超时时间在很多语言是可以做到的。比如golang 中的 TimeoutContext。

而第二种方法,就是我们先为锁设置一个比较小的超时时长,然后不断续期这个锁。对一个锁的不断需求,也可以理解为重新开始加锁,这种可以不断续期的锁,就叫做可重入锁。

除了主线程之外,可重入锁必然有一个另外的线程(或者携程)可以对这个锁进行续期,我们叫这个额外的程序叫做watchDog(看门狗)。

具体实现

在Golang中,语言级别天生支持协程,所以这种可重入锁就非常容易实现:

// DistributeLockRedis 基于redis的分布式可重入锁,自动续租type DistributeLockRedis struct {key       string             // 锁的keyexpire    int64              // 锁超时时间status    bool               // 上锁成功标识cancelFun context.CancelFunc // 用于取消自动续租携程redis     redis.Client       // redis句柄}// 创建可func NewDistributeLockRedis(key string, expire int64) *DistributeLockRedis {return &DistributeLockRedis{ key : key, expire : expire,}}// TryLock 上锁func (dl *DistributeLockRedis) TryLock() (err error) {if err = dl.lock(); err != nil {return err}ctx, cancelFun := context.WithCancel(context.Background())dl.cancelFun = cancelFundl.startWatchDog(ctx) // 创建守护协程,自动对锁进行续期dl.status = truereturn nil}// competition 竞争锁func (dl *DistributeLockRedis) lock() error {if res, err := redis.String(dl.redis.Do(context.Background(), "SET", dl.key, 1, "NX", "EX", dl.expire)); err != nil {return err} return nil}// guard 创建守护协程,自动续期func (dl *DistributeLockRedis) startWatchDog(ctx context.Context) {safeGo(func() error {for {select {// Unlock通知结束case <-ctx.Done():return nildefault:// 否则只要开始了,就自动重入(续租锁)if dl.status {if res, err := redis.Int(dl.redis.Do(context.Background(), "EXPIRE", dl.key, dl.expire)); err != nil {return nil} // 续租时间为 expire/2 秒time.Sleep(time.Duration(dl.expire/2) * time.Second)}}}})}// Unlock 释放锁func (dl *DistributeLockRedis) Unlock() (err error) {// 这个重入锁必须取消,放在第一个地方执行if dl.cancelFun != nil {dl.cancelFun() // 释放成功,取消重入锁}var res intif dl.status {if res, err = redis.Int(dl.redis.Do(context.Background(), "Del", dl.key)); err != nil {return fmt.Errorf("释放锁失败")}if res == 1 {dl.status = falsereturn nil}}return fmt.Errorf("释放锁失败")}

这段代码的逻辑基本上都以注释的形式来写了。其中主要就在startWatchDog,对锁进行重新续期

ctx, cancelFun := context.WithCancel(context.Background())dl.cancelFun = cancelFundl.startWatchDog(ctx) // 创建守护协程,自动对锁进行续期dl.status = true

首先创建一个cancelContext,它的context函数cancelFunc是给Unlock进行调用的。然后启动一个goroutine进程来循环续期。

这个新启动的goroutine在主goroutine处理结束,调用Unlock的时候,才会结束,否则会在 过期时间/2 的时候,调用一次redis的expire命令来进行续期。

至于外部,在使用的时候如下

func Foo() error {  key := foo    // 创建可重入的分布式锁dl := NewDistributeLockRedis(key, 10)// 争抢锁err := dl.TryLock()if err != nil {// 没有抢到锁return err}// 抢到锁的记得释放锁defer func() {dl.Unlock()}// 做真正的任务DoJob()}

读到这里,这篇“Golang怎么实现可重入锁”文章已经介绍完毕,想要掌握这篇文章的知识点还需要大家自己动手实践使用过才能领会,如果想了解更多相关内容的文章,欢迎关注编程网行业资讯频道。

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯