基于context实现go的单机锁带过期时间

1 数据结构

type ExpireLock struct {
	// 核心锁
	mutex sync.Mutex
	// 流程锁,防止多次解锁,例如异步解锁协程解锁和手动解锁同时发生
	processMutex sync.Mutex
	// 锁的身份标识
	token string
	// 停止异步解锁协程函数
	stop context.CancelFunc
}

2 总体流程

context原理可以看go的context原理_lunar@qq.com的博客-CSDN博客

Lock()方法流程如下:

  1. 加核心锁mutex.Lock()
  2. 设置锁的身份标识token -- 标识这把锁的主人是谁
  3. 检查过期时间,如果小于等于0,就表示手动释放锁,不需开启异步超时解锁协程,大于0就开启一个异步超时解锁协程,过期自动解锁(这里启动的异步协程主要由父context来控制其生命周期)
  4. 给终止异步协程函数stop赋值,启动异步协程,达到指定时间后执行解锁操作

Unlock()方法流程如下:

  1. 先加流程锁processMutex.Lock(),防止并发情况下,Lock方法开启的异步超时解锁协程过期时间到了开始执行释放锁操作和手动解锁同时发生
  2. 校验token
  3. 调用stop函数,停止Lock方法开启的异步超时解锁协程
  4. 重置token
  5. 解锁

3 源码

package redis_lock_test

import (
	"context"
	"errors"
	"redis-lock-test/utils"
	"sync"
	"time"
)

type ExpireLock struct {
	// 核心锁
	mutex sync.Mutex
	// 流程锁,防止多次解锁,例如异步解锁协程解锁和手动解锁同时发生
	processMutex sync.Mutex
	// 锁的身份标识
	token string
	// 停止异步解锁协程函数
	stop context.CancelFunc
}

func NewExpireLock() *ExpireLock {
	return &ExpireLock{}
}

// Lock 加锁
func (e *ExpireLock) Lock(expireTime int64) {
	// 1. 加锁
	e.mutex.Lock()
	// 2,设置锁的身份标识token
	token := utils.GetProcessAndGoroutineIDStr()
	e.token = token

	// 2.1 校验一下过期时间,如果小于等于0,就代表手动释放锁,无需开启异步解锁协程
	if expireTime <= 0 {
		return
	}

	// 3.给终止异步协程函数stop赋值,启动异步协程,达到指定时间后执行解锁操作
	ctx, cancel := context.WithCancel(context.Background())
	e.stop = cancel
	go func() {
		select {
		// 到了锁的过期时间,释放锁
		case <-time.After(time.Duration(expireTime) * time.Second):
			e.unlock(token)
		case <-ctx.Done():
		}
	}()
}

// Unlock 解锁
func (e *ExpireLock) Unlock() error {
	return e.unlock(utils.GetProcessAndGoroutineIDStr())
}

func (e *ExpireLock) unlock(token string) error {
	// 1.加流程锁,防止并发情况下,异步解锁协程解锁和手动解锁同时发生
	e.processMutex.Lock()
	defer e.processMutex.Unlock()

	// 2.校验token
	if e.token != token {
		return errors.New("unlock not your lock")
	}

	// 3.停止异步解锁协程
	if e.stop != nil {
		e.stop()
	}

	// 4.重置token
	e.token = ""

	// 5.解锁
	e.mutex.Unlock()

	return nil
}




// utils包
package utils

import (
	"fmt"
	"os"
	"runtime"
	"strconv"
	"strings"
)

func GetCurrentProcessID() string {
	return strconv.Itoa(os.Getpid())
}

// GetCurrentGoroutineID 获取当前的协程ID
func GetCurrentGoroutineID() string {
	buf := make([]byte, 128)
	buf = buf[:runtime.Stack(buf, false)]
	stackInfo := string(buf)
	return strings.TrimSpace(strings.Split(strings.Split(stackInfo, "[running]")[0], "goroutine")[1])
}

func GetProcessAndGoroutineIDStr() string {
	return fmt.Sprintf("%s_%s", GetCurrentProcessID(), GetCurrentGoroutineID())
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值