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 // 锁的key
expire 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 = cancelFun
dl.startWatchDog(ctx) // 创建守护协程,自动对锁进行续期
dl.status = true
return 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 nil
default:
// 否则只要开始了,就自动重入(续租锁)
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 int
if 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 = false
return nil
}
}
return fmt.Errorf("释放锁失败")
}

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

ctx, cancelFun := context.WithCancel(context.Background())
dl.cancelFun = cancelFun
dl.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()
}

参考文章

如果还想了解更多,以下的参考文章值得阅读。

redisson

https://github.com/redisson/redisson

滴滴 曾奇:谈谈我所认识的分布式锁

http://blog.itpub.net/69908606/viewspace-2644366/

Redis 分布式锁|从青铜到钻石的五种演进方案

https://my.oschina.net/u/4499317/blog/5039486

分布式锁中的王者方案 - Redisson

https://xie.infoq.cn/article/d8e897f768eb1a358a0fd6300#:~:text=Redisson 是一个在Redis,In-Memory Data Grid)。

redisson中的看门狗机制总结

https://www.cnblogs.com/jelly12345/p/14699492.html

Redis分布式锁如何自动续期

https://blog.csdn.net/yangbindxj/article/details/123189395

到底什么是重入锁,拜托,一次搞清楚!

https://zhuanlan.zhihu.com/p/71018541

Golang可重入锁的实现的更多相关文章

  1. Android 死锁和重入锁

    死锁的定义: 1.一般的死锁 一般的死锁是指多个线程的执行必须同时拥有多个资源,由于不同的线程需要的资源被不同的线程占用,最终导致僵持的状态,这就是一般死锁的定义. package com.cxt.t ...

  2. 可重入锁 公平锁 读写锁、CLH队列、CLH队列锁、自旋锁、排队自旋锁、MCS锁、CLH锁

    1.可重入锁 如果锁具备可重入性,则称作为可重入锁. ========================================== (转)可重入和不可重入 2011-10-04 21:38 这 ...

  3. java ReentrantLock可重入锁功能

    1.可重入锁是可以中断的,如果发生了死锁,可以中断程序 //如下程序出现死锁,不去kill jvm无法解决死锁 public class Uninterruptible { public static ...

  4. synchronized关键字,Lock接口以及可重入锁ReentrantLock

    多线程环境下,必须考虑线程同步的问题,这是因为多个线程同时访问变量或者资源时会有线程争用,比如A线程读取了一个变量,B线程也读取了这个变量,然后他们同时对这个变量做了修改,写回到内存中,由于是同时做修 ...

  5. 可重入锁 & 自旋锁 & Java里的AtomicReference和CAS操作 & Linux mutex不可重入

    之前还是写过蛮多的关于锁的文章的: http://www.cnblogs.com/charlesblc/p/5994162.html <[转载]Java中的锁机制 synchronized &a ...

  6. Java并发编程-可重入锁

    可重入锁,也叫做递归锁,指的是同一线程 外层函数获得锁之后 ,内层递归函数仍可以获取该锁而不受影响.在JAVA环境下 ReentrantLock 和synchronized 都是 可重入锁. publ ...

  7. Java中可重入锁ReentrantLock原理剖析

    本文由码农网 – 吴极心原创,转载请看清文末的转载要求,欢迎参与我们的付费投稿计划! 一. 概述 本文首先介绍Lock接口.ReentrantLock的类层次结构以及锁功能模板类AbstractQue ...

  8. ReentrantLock(重入锁)以及公平性

    ReentrantLock(重入锁)以及公平性 标签(空格分隔): java NIO 如果在绝对时间上,先对锁进行获取的请求一定被先满足,那么这个锁是公平的,反之,是不公平的,也就是说等待时间最长的线 ...

  9. 可重入锁(good)

    可重入锁,也叫做递归锁,是指在一个线程中可以多次获取同一把锁,比如:一个线程在执行一个带锁的方法,该方法中又调用了另一个需要相同锁的方法,则该线程可以直接执行调用的方法[即可重入],而无需重新获得锁: ...

随机推荐

  1. Vue.js 开发实践:实现精巧的无限加载与分页功能

    本篇文章是一篇Vue.js的教程,目标在于用一种常见的业务场景--分页/无限加载,帮助读者更好的理解Vue.js中的一些设计思想.与许多Todo List类的入门教程相比,更全面的展示使用Vue.js ...

  2. 彻底搞懂CSS层叠上下文、层叠等级、层叠顺序、z-index

    前言 最近,在项目中遇到一个关于CSS中元素z-index属性的问题,具体问题不太好描述,总结起来就是当给元素和父元素色设置position属性和z-index相关属性后,页面上渲染的元素层级结果和我 ...

  3. CCF201512-2消除类游戏

    问题描述 消除类游戏是深受大众欢迎的一种游戏,游戏在一个包含有n行m列的游戏棋盘上进行,棋盘的每一行每一列的方格上放着一个有颜色的棋子,当一行或一列上有连续三个或更多的相同颜色的棋子时,这些棋子都被消 ...

  4. 142. 环形链表 II

    做题思路 or 感想 : 1,这一题用快慢指针来判断是否有环,快慢指针同一起点,速度不同,如果有环,则必定会相遇 2,第二个有意思的点就是数论环节来弄出环入口了,真的太精妙了,但因为我表述能力不好,这 ...

  5. asp.net core启动源码以及监听,到处理请求响应的过程

    摘要 asp.net core发布至今已经将近6年了,很多人对于这一块还是有些陌生,或者说没接触过:接触过的,对于asp.net core整个启动过程,监听过程,以及请求过程,响应过程也是一知半解,可 ...

  6. Educational Codeforces Round 116 (Rated for Div. 2), problem: (C) Banknotes

    传送门 Problem - C - Codeforces 题目 题目重点内容手打翻译:(欢迎批评指正) 在柏林, 使用着n套不同的货币(banknotes).第i个货币面额为10ai 元,货币的第一种 ...

  7. OV5640图像采集(一)VGA显示

    vga控制器模块 1 引言  项目的背景是采集无人车间现场的工件图像并送往控制间pc端处理,最终实现缺陷检测.项目包括图像采集模块,数据传输模块,上位机,缺陷检测算法等四个部分.其中,图像采集模块又分 ...

  8. 这3个免费PPT素材网站,一定要收藏

    制作PPT,这三个网站的素材绝对够用! 1.象刀设计 https://www.101dao.com 象刀设计里面有非常多PPT模板,这个网站也是主打PPT素材. 分类很清晰,需要什么风格的素材能快速找 ...

  9. docker容器的持久化存储:Volume

    独立于docker容器的持久化存储: 法(1):自动将服务器文件夹挂载到容器内部文件夹/usr/share/nginx/html,这样只修改服务器文件夹下的内容即可对应修改容器内部文件夹的内容 将服务 ...

  10. Java 练手项目

    HotelSystem:https://github.com/misterchaos/HotelSystem(酒店管理系统 Java,tomcat,mysql,servlet,jsp实现,没有使用任何 ...