<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>redis Archives - 原立方</title>
	<atom:link href="https://www.atomic-cube.cn/category/redis/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.atomic-cube.cn/category/redis/</link>
	<description>技术栈的流动-从零到∞</description>
	<lastBuildDate>Fri, 01 May 2026 08:32:19 +0000</lastBuildDate>
	<language>zh-CN</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.0.2</generator>
	<item>
		<title>使用Go语言实现Redis分布式锁（附有看门狗自动续期机制）</title>
		<link>https://www.atomic-cube.cn/%e4%bd%bf%e7%94%a8go%e8%af%ad%e8%a8%80%e5%ae%9e%e7%8e%b0redis%e5%88%86%e5%b8%83%e5%bc%8f%e9%94%81%ef%bc%88%e9%99%84%e6%9c%89%e7%9c%8b%e9%97%a8%e7%8b%97%e8%87%aa%e5%8a%a8%e7%bb%ad%e6%9c%9f%e6%9c%ba/</link>
					<comments>https://www.atomic-cube.cn/%e4%bd%bf%e7%94%a8go%e8%af%ad%e8%a8%80%e5%ae%9e%e7%8e%b0redis%e5%88%86%e5%b8%83%e5%bc%8f%e9%94%81%ef%bc%88%e9%99%84%e6%9c%89%e7%9c%8b%e9%97%a8%e7%8b%97%e8%87%aa%e5%8a%a8%e7%bb%ad%e6%9c%9f%e6%9c%ba/#respond</comments>
		
		<dc:creator><![CDATA[evans]]></dc:creator>
		<pubDate>Thu, 26 Mar 2026 12:52:00 +0000</pubDate>
				<category><![CDATA[Golang]]></category>
		<category><![CDATA[redis]]></category>
		<category><![CDATA[技术文章]]></category>
		<category><![CDATA[Go-zero]]></category>
		<guid isPermaLink="false">https://www.atomic-cube.cn/?p=1446</guid>

					<description><![CDATA[<p>Redis我们日常开发经常使用，而分布式锁的一个重要实现就是通过Redis完成，分布式锁要解决的核心问题是防止对某个资源进行重复或者过度请求，例如我们在分布式系统中创建订单之前，必须获取分布式锁才能创建订单，其要解决的主要问题有两个： 1）如果用户重复点击提交订单按钮，可以通过分布式锁避免重复创建订单 2）商品库存有限，通过分布式锁能够解决超卖问题 Redis实现分布式锁 由于Redis中set nx命令的原子性，只有在键值不存在的时候才能设置值，因此可以通过set nx实现分布式锁，但是它有弊端就是： 如果业务还没有执行完，那么会导致业务没有执行完，锁就被释放了 因此延伸出了基于Redis延伸的Redisson分布式锁框架，它实现的原理在于使用Redis单线程模型执行SET NX命令lua脚本确保获取锁操作原子性，同时它内置了更为丰富的看门狗机制，满足了在业务执行过程中，自动续期锁 Go语言实现分布式锁 上面都是基于理论介绍，接下来就实现基于Redis的分布式锁 明确原子性操作 在实现之前，我们要明确加锁、释放锁与续期锁的机制： 加锁：设置键值与过期时间，通过setnx px实现，如果键值已经存在直接返回错误 释放锁：我们一定要确保释放的锁是自己加的锁，因此要判断value值是否是之前设置的value值，只有判断正确才能够释放锁 续期锁：与释放锁同理，只有当前锁是自己加的锁才续期 完整测试用例： 测试依赖&#160;miniredis&#160;模拟 Redis 服务，请先安装： 测试文件：mutex_test.go 运行测试 -race&#160;标志会启用数据竞争检测，确保并发安全。 注意事项</p>
<p>The post <a href="https://www.atomic-cube.cn/%e4%bd%bf%e7%94%a8go%e8%af%ad%e8%a8%80%e5%ae%9e%e7%8e%b0redis%e5%88%86%e5%b8%83%e5%bc%8f%e9%94%81%ef%bc%88%e9%99%84%e6%9c%89%e7%9c%8b%e9%97%a8%e7%8b%97%e8%87%aa%e5%8a%a8%e7%bb%ad%e6%9c%9f%e6%9c%ba/">使用Go语言实现Redis分布式锁（附有看门狗自动续期机制）</a> appeared first on <a href="https://www.atomic-cube.cn">原立方</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Redis我们日常开发经常使用，而分布式锁的一个重要实现就是通过Redis完成，分布式锁要解决的核心问题是<strong>防止对某个资源进行重复或者过度请求</strong>，例如我们在分布式系统中创建订单之前，必须获取分布式锁才能创建订单，其要解决的主要问题有两个：</p>



<p class="wp-block-paragraph">1）如果用户重复点击提交订单按钮，可以通过分布式锁<strong>避免重复创建订单</strong></p>



<p class="wp-block-paragraph">2）商品库存有限，通过分布式锁能够<strong>解决超卖问题</strong></p>



<p class="wp-block-paragraph">Redis实现分布式锁</p>



<p class="wp-block-paragraph">由于Redis中<code>set nx</code>命令的原子性，只有在键值不存在的时候才能设置值，因此可以通过<code>set nx</code>实现分布式锁，但是它有弊端就是：</p>



<p class="wp-block-paragraph">如果业务还没有执行完，那么会导致业务没有执行完，锁就被释放了</p>



<p class="wp-block-paragraph">因此延伸出了基于Redis延伸的Redisson分布式锁框架，它实现的原理在于<strong>使用Redis单线程模型执行SET NX命令lua脚本</strong>确保获取锁操作原子性，同时它内置了更为丰富的<strong>看门狗机制</strong>，满足了在业务执行过程中，自动续期锁</p>



<h2 class="wp-block-heading" id="2vq7h">Go语言实现分布式锁</h2>



<p class="wp-block-paragraph">上面都是基于理论介绍，接下来就实现基于Redis的分布式锁</p>



<h3 class="wp-block-heading" id="5ts0b">明确原子性操作</h3>



<p class="wp-block-paragraph">在实现之前，我们要明确<strong>加锁、释放锁</strong>与<strong>续期锁</strong>的机制：</p>



<p class="wp-block-paragraph"><strong>加锁</strong>：设置键值与过期时间，通过<code>setnx px</code>实现，如果键值已经存在直接返回错误</p>



<p class="wp-block-paragraph"><strong>释放锁</strong>：我们一定要确保释放的锁是自己加的锁，因此要判断value值是否是之前设置的value值，只有判断正确才能够释放锁</p>



<p class="wp-block-paragraph"><strong>续期锁</strong>：与释放锁同理，只有当前锁是自己加的锁才续期</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(3 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>package utils

import (
	"context"
	"errors"
	"fmt"
	"sync"
	"time"

	"github.com/google/uuid"
	"github.com/redis/go-redis/v9"
)

// 预定义错误，便于调用方进行错误判断
var (
	// ErrLockNotHeld 释放锁时发现锁不属于当前实例
	ErrLockNotHeld = errors.New("lock not held")
	// ErrLockAcquireFailed 加锁失败（因已被他人持有或超时）
	ErrLockAcquireFailed = errors.New("failed to acquire lock")
	// ErrLockLost 锁在持有期间丢失（续期失败），此时业务应中断
	ErrLockLost = errors.New("lock lost during hold")
)

// Logger 可注入的日志接口，便于集成各类日志库
type Logger interface {
	Printf(format string, v ...interface{})
}

// defaultLogger 默认日志实现，输出到标准输出
type defaultLogger struct{}

func (l *defaultLogger) Printf(format string, v ...interface{}) {
	fmt.Printf(format+"\n", v...)
}

// RenewFailedCallback 续期失败时的回调函数，用于通知业务方锁已丢失
type RenewFailedCallback func(key string, err error)

// RetryStrategy 重试策略类型，用于自定义获取锁失败时的等待行为
type RetryStrategy func(attempt int) time.Duration

// Mutex 基于 Redis 的分布式锁，内置看门狗自动续期。
// 每个实例仅允许一次 Lock → Unlock 生命周期，不可复用。
type Mutex struct {
	client redis.UniversalClient // Redis 客户端，支持单机、哨兵、集群
	key    string                // Redis 中锁的键名
	value  string                // 锁持有者唯一标识（UUID）
	ttl    time.Duration         // 锁的过期时间

	// 状态保护
	mu     sync.Mutex
	locked bool   // 是否已成功加锁
	lost   bool   // 锁是否已丢失（续期失败导致），丢失后不应再尝试释放
	stopCh chan struct{} // 通知看门狗退出的通道
	wg     sync.WaitGroup

	// 看门狗启动保护
	watchdogOnce sync.Once // 确保看门狗只启动一次

	// 配置项
	retryStrategy     RetryStrategy      // 自定义重试策略
	minRenewInterval  time.Duration      // 最小续期间隔，防止高频续期
	renewTimeout      time.Duration      // 续期操作的超时时间
	logger            Logger             // 日志接口
	renewFailedCb     RenewFailedCallback // 续期失败回调
}

// Option 函数式配置选项
type Option func(*Mutex)

// WithRetryDelay 设置固定间隔的重试策略（简单场景）
func WithRetryDelay(d time.Duration) Option {
	return func(m *Mutex) {
		m.retryStrategy = func(attempt int) time.Duration {
			return d
		}
	}
}

// WithExponentialBackoff 设置指数退避重试策略
// 初始间隔为 initial，最大间隔为 max，每次重试间隔翻倍
func WithExponentialBackoff(initial, max time.Duration) Option {
	return func(m *Mutex) {
		m.retryStrategy = func(attempt int) time.Duration {
			d := initial
			for i := 0; i &lt; attempt; i++ {
				d *= 2
				if d > max {
					return max
				}
			}
			return d
		}
	}
}

// WithMinRenewInterval 设置看门狗最小续期间隔，避免高频续期（默认 200ms）
func WithMinRenewInterval(d time.Duration) Option {
	return func(m *Mutex) {
		m.minRenewInterval = d
	}
}

// WithRenewTimeout 设置单次续期操作的超时时间（默认 2s）
func WithRenewTimeout(d time.Duration) Option {
	return func(m *Mutex) {
		m.renewTimeout = d
	}
}

// WithLogger 注入自定义日志实现
func WithLogger(l Logger) Option {
	return func(m *Mutex) {
		m.logger = l
	}
}

// WithRenewFailedCallback 设置续期失败回调
func WithRenewFailedCallback(cb RenewFailedCallback) Option {
	return func(m *Mutex) {
		m.renewFailedCb = cb
	}
}

// NewMutex 创建一个新的分布式锁实例
// client: Redis 客户端
// key: 锁的键名，建议使用业务唯一标识，如 "order:lock:123"
// ttl: 锁的过期时间，建议设置为业务处理时间的 2~3 倍
func NewMutex(client redis.UniversalClient, key string, ttl time.Duration, opts ...Option) *Mutex {
	if ttl &lt;= 0 {
		ttl = 30 * time.Second
	}
	m := &amp;Mutex{
		client:           client,
		key:              key,
		value:            uuid.New().String(), // 生成唯一持有者标识
		ttl:              ttl,
		stopCh:           make(chan struct{}),
		retryStrategy:    func(attempt int) time.Duration { return 50 * time.Millisecond }, // 默认固定 50ms
		minRenewInterval: 200 * time.Millisecond,
		renewTimeout:     2 * time.Second,
		logger:           &amp;defaultLogger{},
	}
	for _, opt := range opts {
		opt(m)
	}
	return m
}

// Lock 阻塞式获取锁，直到成功或 ctx 超时/取消。
// 成功获取锁后自动启动看门狗进行续期。
func (m *Mutex) Lock(ctx context.Context) error {
	attempt := 0
	for {
		ok, err := m.tryAcquire(ctx)
		if err != nil {
			return err
		}
		if ok {
			// 加锁成功，启动看门狗
			m.startWatchdog()
			return nil
		}

		// 未获取到锁，计算等待时间
		delay := m.retryStrategy(attempt)
		attempt++

		select {
		case &lt;-ctx.Done():
			return fmt.Errorf("lock %s acquire timeout: %w", m.key, ctx.Err())
		case &lt;-time.After(delay):
			// 继续重试
		}
	}
}

// TryLock 非阻塞尝试获取一次锁，立即返回结果。
// 若成功获取，会自动启动看门狗。
func (m *Mutex) TryLock(ctx context.Context) (bool, error) {
	ok, err := m.tryAcquire(ctx)
	if err != nil {
		return false, err
	}
	if ok {
		m.startWatchdog()
		return true, nil
	}
	return false, nil
}

// Unlock 释放锁，并停止看门狗。
// 只有锁的当前持有者才能成功释放，否则返回 ErrLockNotHeld。
// 若锁在持有期间已丢失（续期失败），则返回 ErrLockLost。
func (m *Mutex) Unlock(ctx context.Context) error {
	// 先停止看门狗，确保不再有续期操作
	m.stopWatchdog()

	m.mu.Lock()
	defer m.mu.Unlock()

	if m.lost {
		return ErrLockLost
	}
	if !m.locked {
		return ErrLockNotHeld
	}

	err := m.releaseLock(ctx)
	if err == nil {
		m.locked = false
	}
	return err
}

// IsHeld 检查当前实例是否仍持有锁（通过 Redis 实时验证）
func (m *Mutex) IsHeld(ctx context.Context) (bool, error) {
	val, err := m.client.Get(ctx, m.key).Result()
	if err == redis.Nil {
		return false, nil
	}
	if err != nil {
		return false, err
	}
	return val == m.value, nil
}

// tryAcquire 执行一次加锁尝试，内部更新 locked 状态
func (m *Mutex) tryAcquire(ctx context.Context) (bool, error) {
	m.mu.Lock()
	defer m.mu.Unlock()

	if m.locked {
		return false, fmt.Errorf("mutex already locked")
	}

	ok, err := m.client.SetNX(ctx, m.key, m.value, m.ttl).Result()
	if err != nil {
		return false, err
	}
	if ok {
		m.locked = true
		m.lost = false
		return true, nil
	}
	return false, nil
}

// releaseLock 执行 Redis 释放锁的 Lua 脚本，确保原子性
func (m *Mutex) releaseLock(ctx context.Context) error {
	script := `
		if redis.call("GET", KEYS&#91;1&#93;) == ARGV&#91;1&#93; then
			return redis.call("DEL", KEYS&#91;1&#93;)
		else
			return 0
		end
	`
	result, err := m.client.Eval(ctx, script, []string{m.key}, m.value).Result()
	if err != nil {
		return fmt.Errorf("redis eval error: %w", err)
	}
	if result.(int64) == 0 {
		return ErrLockNotHeld
	}
	return nil
}

// startWatchdog 启动后台续期协程（内部使用 Once 保证只启动一次）
func (m *Mutex) startWatchdog() {
	m.watchdogOnce.Do(func() {
		m.wg.Add(1)
		go m.watchdogLoop()
	})
}

// watchdogLoop 看门狗主循环，定期续期
func (m *Mutex) watchdogLoop() {
	defer m.wg.Done()

	// 计算续期间隔：TTL 的 1/3，且不小于最小间隔
	interval := m.ttl / 3
	if interval &lt; m.minRenewInterval {
		interval = m.minRenewInterval
	}

	ticker := time.NewTicker(interval)
	defer ticker.Stop()

	for {
		select {
		case &lt;-ticker.C:
			if err := m.renew(); err != nil {
				// 续期失败，标记锁已丢失，并通知外部
				m.markLost()
				m.logger.Printf("watchdog renew failed for key %s: %v", m.key, err)
				if m.renewFailedCb != nil {
					m.renewFailedCb(m.key, err)
				}
				return
			}
		case &lt;-m.stopCh:
			// 收到停止信号，正常退出
			return
		}
	}
}

// renew 执行一次续期操作，使用 Lua 脚本验证持有者身份并延长 TTL
func (m *Mutex) renew() error {
	ctx, cancel := context.WithTimeout(context.Background(), m.renewTimeout)
	defer cancel()

	script := `
		if redis.call("GET", KEYS&#91;1&#93;) == ARGV&#91;1&#93; then
			return redis.call("EXPIRE", KEYS&#91;1&#93;, ARGV&#91;2&#93;)
		else
			return 0
		end
	`
	result, err := m.client.Eval(ctx, script, []string{m.key}, m.value, int64(m.ttl.Seconds())).Result()
	if err != nil {
		return err
	}
	if result.(int64) == 0 {
		return errors.New("lock not held or key missing")
	}
	return nil
}

// markLost 标记锁已丢失，由续期失败时调用
func (m *Mutex) markLost() {
	m.mu.Lock()
	defer m.mu.Unlock()
	m.lost = true
	m.locked = false // 丢失后即认为不再持有锁
}

// stopWatchdog 停止看门狗协程并等待其退出
func (m *Mutex) stopWatchdog() {
	// 关闭 stopCh 通知看门狗退出（使用 select 避免重复关闭）
	select {
	case &lt;-m.stopCh:
		// 已经关闭
	default:
		close(m.stopCh)
	}
	// 等待看门狗协程完全退出
	m.wg.Wait()
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">package</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">utils</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">import</span><span style="color: #D8DEE9FF"> (</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">context</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">errors</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">fmt</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">sync</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">time</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">github.com/google/uuid</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">github.com/redis/go-redis/v9</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// 预定义错误，便于调用方进行错误判断</span></span>
<span class="line"><span style="color: #81A1C1">var</span><span style="color: #D8DEE9FF"> (</span></span>
<span class="line"><span style="color: #ECEFF4">	</span><span style="color: #616E88">// ErrLockNotHeld 释放锁时发现锁不属于当前实例</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">ErrLockNotHeld</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">errors</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">New</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">lock not held</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #ECEFF4">	</span><span style="color: #616E88">// ErrLockAcquireFailed 加锁失败（因已被他人持有或超时）</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">ErrLockAcquireFailed</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">errors</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">New</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">failed to acquire lock</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #ECEFF4">	</span><span style="color: #616E88">// ErrLockLost 锁在持有期间丢失（续期失败），此时业务应中断</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">ErrLockLost</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">errors</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">New</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">lock lost during hold</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// Logger 可注入的日志接口，便于集成各类日志库</span></span>
<span class="line"><span style="color: #81A1C1">type</span><span style="color: #D8DEE9FF"> Logger interface {</span></span>
<span class="line"><span style="color: #D8DEE9FF">	Printf(format string, v ...interface{})</span></span>
<span class="line"><span style="color: #D8DEE9FF">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// defaultLogger 默认日志实现，输出到标准输出</span></span>
<span class="line"><span style="color: #81A1C1">type</span><span style="color: #D8DEE9FF"> defaultLogger struct{}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF"> (</span><span style="color: #D8DEE9">l</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">defaultLogger</span><span style="color: #D8DEE9FF">) </span><span style="color: #88C0D0">Printf</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">format</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">string</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">v</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">...</span><span style="color: #D8DEE9">interface</span><span style="color: #ECEFF4">{}</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Printf</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">format</span><span style="color: #81A1C1">+</span><span style="color: #ECEFF4">&quot;</span><span style="color: #EBCB8B">\n</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">v</span><span style="color: #81A1C1">...</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// RenewFailedCallback 续期失败时的回调函数，用于通知业务方锁已丢失</span></span>
<span class="line"><span style="color: #81A1C1">type</span><span style="color: #D8DEE9FF"> RenewFailedCallback func(key string, err error)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// RetryStrategy 重试策略类型，用于自定义获取锁失败时的等待行为</span></span>
<span class="line"><span style="color: #81A1C1">type</span><span style="color: #D8DEE9FF"> RetryStrategy func(attempt int) time.Duration</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// Mutex 基于 Redis 的分布式锁，内置看门狗自动续期。</span></span>
<span class="line"><span style="color: #616E88">// 每个实例仅允许一次 Lock → Unlock 生命周期，不可复用。</span></span>
<span class="line"><span style="color: #81A1C1">type</span><span style="color: #D8DEE9FF"> Mutex struct {</span></span>
<span class="line"><span style="color: #D8DEE9FF">	client redis.UniversalClient </span><span style="color: #616E88">// Redis 客户端，支持单机、哨兵、集群</span></span>
<span class="line"><span style="color: #D8DEE9FF">	key    string                </span><span style="color: #616E88">// Redis 中锁的键名</span></span>
<span class="line"><span style="color: #D8DEE9FF">	value  string                </span><span style="color: #616E88">// 锁持有者唯一标识（UUID）</span></span>
<span class="line"><span style="color: #D8DEE9FF">	ttl    time.Duration         </span><span style="color: #616E88">// 锁的过期时间</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">	</span><span style="color: #616E88">// 状态保护</span></span>
<span class="line"><span style="color: #D8DEE9FF">	mu     sync.Mutex</span></span>
<span class="line"><span style="color: #D8DEE9FF">	locked bool   </span><span style="color: #616E88">// 是否已成功加锁</span></span>
<span class="line"><span style="color: #D8DEE9FF">	lost   bool   </span><span style="color: #616E88">// 锁是否已丢失（续期失败导致），丢失后不应再尝试释放</span></span>
<span class="line"><span style="color: #D8DEE9FF">	stopCh chan struct{} </span><span style="color: #616E88">// 通知看门狗退出的通道</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">wg</span><span style="color: #D8DEE9FF">     </span><span style="color: #D8DEE9">sync</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">WaitGroup</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">	</span><span style="color: #616E88">// 看门狗启动保护</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">watchdogOnce</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">sync</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Once</span><span style="color: #D8DEE9FF"> </span><span style="color: #616E88">// 确保看门狗只启动一次</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">	</span><span style="color: #616E88">// 配置项</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">retryStrategy</span><span style="color: #D8DEE9FF">     </span><span style="color: #D8DEE9">RetryStrategy</span><span style="color: #D8DEE9FF">      </span><span style="color: #616E88">// 自定义重试策略</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">minRenewInterval</span><span style="color: #D8DEE9FF">  </span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Duration</span><span style="color: #D8DEE9FF">      </span><span style="color: #616E88">// 最小续期间隔，防止高频续期</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">renewTimeout</span><span style="color: #D8DEE9FF">      </span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Duration</span><span style="color: #D8DEE9FF">      </span><span style="color: #616E88">// 续期操作的超时时间</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">logger</span><span style="color: #D8DEE9FF">            </span><span style="color: #D8DEE9">Logger</span><span style="color: #D8DEE9FF">             </span><span style="color: #616E88">// 日志接口</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">renewFailedCb</span><span style="color: #D8DEE9FF">     </span><span style="color: #D8DEE9">RenewFailedCallback</span><span style="color: #D8DEE9FF"> </span><span style="color: #616E88">// 续期失败回调</span></span>
<span class="line"><span style="color: #D8DEE9FF">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// Option 函数式配置选项</span></span>
<span class="line"><span style="color: #81A1C1">type</span><span style="color: #D8DEE9FF"> Option func(*Mutex)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// WithRetryDelay 设置固定间隔的重试策略（简单场景）</span></span>
<span class="line"><span style="color: #D8DEE9FF">func WithRetryDelay(d time.Duration) Option {</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">m</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">Mutex</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		m.</span><span style="color: #D8DEE9">retryStrategy</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">attempt</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">int</span><span style="color: #D8DEE9FF">) </span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Duration</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">			return </span><span style="color: #D8DEE9">d</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// WithExponentialBackoff 设置指数退避重试策略</span></span>
<span class="line"><span style="color: #616E88">// 初始间隔为 initial，最大间隔为 max，每次重试间隔翻倍</span></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">WithExponentialBackoff</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">initial</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">max</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Duration</span><span style="color: #D8DEE9FF">) </span><span style="color: #D8DEE9">Option</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">m</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">Mutex</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		m.</span><span style="color: #D8DEE9">retryStrategy</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">attempt</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">int</span><span style="color: #D8DEE9FF">) </span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Duration</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">			</span><span style="color: #88C0D0">d </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">initial</span></span>
<span class="line"><span style="color: #D8DEE9FF">			</span><span style="color: #D8DEE9">for</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">i</span><span style="color: #D8DEE9FF"> :</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span><span style="color: #D8DEE9FF">; </span><span style="color: #D8DEE9">i</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">attempt</span><span style="color: #D8DEE9FF">; </span><span style="color: #D8DEE9">i</span><span style="color: #81A1C1">++</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">				d *= </span><span style="color: #B48EAD">2</span></span>
<span class="line"><span style="color: #88C0D0">				if d &gt; max {</span></span>
<span class="line"><span style="color: #88C0D0">					return max</span></span>
<span class="line"><span style="color: #D8DEE9FF">				</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">			</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">			return </span><span style="color: #D8DEE9">d</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// WithMinRenewInterval 设置看门狗最小续期间隔，避免高频续期（默认 200ms）</span></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">WithMinRenewInterval</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">d</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Duration</span><span style="color: #D8DEE9FF">) </span><span style="color: #D8DEE9">Option</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">m</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">Mutex</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		m.</span><span style="color: #D8DEE9">minRenewInterval</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">d</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// WithRenewTimeout 设置单次续期操作的超时时间（默认 2s）</span></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">WithRenewTimeout</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">d</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Duration</span><span style="color: #D8DEE9FF">) </span><span style="color: #D8DEE9">Option</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">m</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">Mutex</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		m.</span><span style="color: #D8DEE9">renewTimeout</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">d</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// WithLogger 注入自定义日志实现</span></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">WithLogger</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">l</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">Logger</span><span style="color: #D8DEE9FF">) </span><span style="color: #D8DEE9">Option</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">m</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">Mutex</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		m.</span><span style="color: #D8DEE9">logger</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">l</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// WithRenewFailedCallback 设置续期失败回调</span></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">WithRenewFailedCallback</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">cb</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">RenewFailedCallback</span><span style="color: #D8DEE9FF">) </span><span style="color: #D8DEE9">Option</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">m</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">Mutex</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		m.</span><span style="color: #D8DEE9">renewFailedCb</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">cb</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// NewMutex 创建一个新的分布式锁实例</span></span>
<span class="line"><span style="color: #616E88">// client: Redis 客户端</span></span>
<span class="line"><span style="color: #616E88">// key: 锁的键名，建议使用业务唯一标识，如 &quot;order:lock:123&quot;</span></span>
<span class="line"><span style="color: #616E88">// ttl: 锁的过期时间，建议设置为业务处理时间的 2~3 倍</span></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">NewMutex</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">client</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">redis</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">UniversalClient</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">key</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">string</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">ttl</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Duration</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">opts</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">...</span><span style="color: #D8DEE9">Option</span><span style="color: #D8DEE9FF">) </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">Mutex</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">ttl</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&lt;=</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #D8DEE9">ttl</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">30</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Second</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">	m </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&amp;</span><span style="color: #D8DEE9">Mutex</span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		client</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF">           </span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">		key</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF">              </span><span style="color: #D8DEE9">key</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">		value</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF">            </span><span style="color: #D8DEE9">uuid</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">New</span><span style="color: #D8DEE9FF">()</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">String</span><span style="color: #D8DEE9FF">()</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #616E88">// 生成唯一持有者标识</span></span>
<span class="line"><span style="color: #D8DEE9FF">		ttl</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF">              </span><span style="color: #D8DEE9">ttl</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">		stopCh</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF">           </span><span style="color: #88C0D0">make</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">chan</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">struct</span><span style="color: #ECEFF4">{}</span><span style="color: #D8DEE9FF">)</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">		retryStrategy</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF">    </span><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">attempt</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">int</span><span style="color: #D8DEE9FF">) </span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Duration</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">50</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Millisecond</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">},</span><span style="color: #D8DEE9FF"> </span><span style="color: #616E88">// 默认固定 50ms</span></span>
<span class="line"><span style="color: #D8DEE9FF">		minRenewInterval</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">200</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Millisecond</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">		renewTimeout</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF">     </span><span style="color: #B48EAD">2</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Second</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">		logger</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF">           </span><span style="color: #81A1C1">&amp;</span><span style="color: #D8DEE9">defaultLogger</span><span style="color: #ECEFF4">{},</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">for</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">_</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> opt </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">range</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">opts</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #88C0D0">opt</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">m</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">m</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// Lock 阻塞式获取锁，直到成功或 ctx 超时/取消。</span></span>
<span class="line"><span style="color: #616E88">// 成功获取锁后自动启动看门狗进行续期。</span></span>
<span class="line"><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF"> (</span><span style="color: #D8DEE9">m</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">Mutex</span><span style="color: #D8DEE9FF">) </span><span style="color: #88C0D0">Lock</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">context</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Context</span><span style="color: #D8DEE9FF">) </span><span style="color: #D8DEE9">error</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	attempt </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">for</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #D8DEE9">ok</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">tryAcquire</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">			</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">ok</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #ECEFF4">			</span><span style="color: #616E88">// 加锁成功，启动看门狗</span></span>
<span class="line"><span style="color: #D8DEE9FF">			</span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">startWatchdog</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">			</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">		</span><span style="color: #616E88">// 未获取到锁，计算等待时间</span></span>
<span class="line"><span style="color: #D8DEE9FF">		delay </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">retryStrategy</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">attempt</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #D8DEE9">attempt</span><span style="color: #81A1C1">++</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #D8DEE9">select</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">case</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&lt;-</span><span style="color: #D8DEE9">ctx</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Done</span><span style="color: #D8DEE9FF">():</span></span>
<span class="line"><span style="color: #D8DEE9FF">			</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Errorf</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">lock %s acquire timeout: %w</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">key</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">ctx</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Err</span><span style="color: #D8DEE9FF">())</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">case</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&lt;-</span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">After</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">delay</span><span style="color: #D8DEE9FF">):</span></span>
<span class="line"><span style="color: #ECEFF4">			</span><span style="color: #616E88">// 继续重试</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// TryLock 非阻塞尝试获取一次锁，立即返回结果。</span></span>
<span class="line"><span style="color: #616E88">// 若成功获取，会自动启动看门狗。</span></span>
<span class="line"><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF"> (</span><span style="color: #D8DEE9">m</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">Mutex</span><span style="color: #D8DEE9FF">) </span><span style="color: #88C0D0">TryLock</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">context</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Context</span><span style="color: #D8DEE9FF">) (</span><span style="color: #D8DEE9">bool</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">error</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">ok</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">tryAcquire</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">false</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">ok</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">startWatchdog</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">true</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">false</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// Unlock 释放锁，并停止看门狗。</span></span>
<span class="line"><span style="color: #616E88">// 只有锁的当前持有者才能成功释放，否则返回 ErrLockNotHeld。</span></span>
<span class="line"><span style="color: #616E88">// 若锁在持有期间已丢失（续期失败），则返回 ErrLockLost。</span></span>
<span class="line"><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF"> (</span><span style="color: #D8DEE9">m</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">Mutex</span><span style="color: #D8DEE9FF">) </span><span style="color: #88C0D0">Unlock</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">context</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Context</span><span style="color: #D8DEE9FF">) </span><span style="color: #D8DEE9">error</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #ECEFF4">	</span><span style="color: #616E88">// 先停止看门狗，确保不再有续期操作</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">stopWatchdog</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">mu</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Lock</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">mu</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Unlock</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">lost</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">ErrLockLost</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!</span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">locked</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">ErrLockNotHeld</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">	err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">releaseLock</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">==</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">locked</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">false</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// IsHeld 检查当前实例是否仍持有锁（通过 Redis 实时验证）</span></span>
<span class="line"><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF"> (</span><span style="color: #D8DEE9">m</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">Mutex</span><span style="color: #D8DEE9FF">) </span><span style="color: #88C0D0">IsHeld</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">context</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Context</span><span style="color: #D8DEE9FF">) (</span><span style="color: #D8DEE9">bool</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">error</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">val</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Get</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">key</span><span style="color: #D8DEE9FF">)</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Result</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">==</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">redis</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">false</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">false</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">val</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">==</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">value</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// tryAcquire 执行一次加锁尝试，内部更新 locked 状态</span></span>
<span class="line"><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF"> (</span><span style="color: #D8DEE9">m</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">Mutex</span><span style="color: #D8DEE9FF">) </span><span style="color: #88C0D0">tryAcquire</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">context</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Context</span><span style="color: #D8DEE9FF">) (</span><span style="color: #D8DEE9">bool</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">error</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">mu</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Lock</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">mu</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Unlock</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">locked</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">false</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Errorf</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">mutex already locked</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">ok</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">SetNX</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">key</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">value</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">ttl</span><span style="color: #D8DEE9FF">)</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Result</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">false</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">ok</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">locked</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">true</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">lost</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">false</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">true</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">false</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// releaseLock 执行 Redis 释放锁的 Lua 脚本，确保原子性</span></span>
<span class="line"><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF"> (</span><span style="color: #D8DEE9">m</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">Mutex</span><span style="color: #D8DEE9FF">) </span><span style="color: #88C0D0">releaseLock</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">context</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Context</span><span style="color: #D8DEE9FF">) </span><span style="color: #D8DEE9">error</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	script </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">`</span></span>
<span class="line"><span style="color: #A3BE8C">		if redis.call(&quot;GET&quot;, KEYS&#91;1&#93;) == ARGV&#91;1&#93; then</span></span>
<span class="line"><span style="color: #A3BE8C">			return redis.call(&quot;DEL&quot;, KEYS&#91;1&#93;)</span></span>
<span class="line"><span style="color: #A3BE8C">		else</span></span>
<span class="line"><span style="color: #A3BE8C">			return 0</span></span>
<span class="line"><span style="color: #A3BE8C">		end</span></span>
<span class="line"><span style="color: #A3BE8C">	</span><span style="color: #ECEFF4">`</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">result</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Eval</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">script</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> []</span><span style="color: #D8DEE9">string</span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF">m.</span><span style="color: #D8DEE9">key</span><span style="color: #ECEFF4">},</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">value</span><span style="color: #D8DEE9FF">)</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Result</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">fmt</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Errorf</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">redis eval error: %w</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">result</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">int64</span><span style="color: #D8DEE9FF">) </span><span style="color: #81A1C1">==</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">ErrLockNotHeld</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// startWatchdog 启动后台续期协程（内部使用 Once 保证只启动一次）</span></span>
<span class="line"><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF"> (</span><span style="color: #D8DEE9">m</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">Mutex</span><span style="color: #D8DEE9FF">) </span><span style="color: #88C0D0">startWatchdog</span><span style="color: #D8DEE9FF">() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">watchdogOnce</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Do</span><span style="color: #D8DEE9FF">(</span><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF">() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		m.wg.Add(</span><span style="color: #B48EAD">1</span><span style="color: #88C0D0">)</span></span>
<span class="line"><span style="color: #88C0D0">		go m.watchdogLoop</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// watchdogLoop 看门狗主循环，定期续期</span></span>
<span class="line"><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF"> (</span><span style="color: #D8DEE9">m</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">Mutex</span><span style="color: #D8DEE9FF">) </span><span style="color: #88C0D0">watchdogLoop</span><span style="color: #D8DEE9FF">() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">wg</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Done</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">	</span><span style="color: #616E88">// 计算续期间隔：TTL 的 1/3，且不小于最小间隔</span></span>
<span class="line"><span style="color: #D8DEE9FF">	interval </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">ttl</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">3</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">interval</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">minRenewInterval</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #D8DEE9">interval</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">minRenewInterval</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">	ticker </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">NewTicker</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">interval</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">ticker</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Stop</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">for</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #D8DEE9">select</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">case</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&lt;-</span><span style="color: #D8DEE9">ticker</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">C</span><span style="color: #D8DEE9FF">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">			</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">renew</span><span style="color: #D8DEE9FF">()</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #ECEFF4">				</span><span style="color: #616E88">// 续期失败，标记锁已丢失，并通知外部</span></span>
<span class="line"><span style="color: #D8DEE9FF">				</span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">markLost</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">				</span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">logger</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Printf</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">watchdog renew failed for key %s: %v</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">key</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">				</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">renewFailedCb</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">					</span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">renewFailedCb</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">key</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">				</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">				</span><span style="color: #81A1C1">return</span></span>
<span class="line"><span style="color: #D8DEE9FF">			</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">case</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&lt;-</span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">stopCh</span><span style="color: #D8DEE9FF">:</span></span>
<span class="line"><span style="color: #ECEFF4">			</span><span style="color: #616E88">// 收到停止信号，正常退出</span></span>
<span class="line"><span style="color: #D8DEE9FF">			</span><span style="color: #81A1C1">return</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// renew 执行一次续期操作，使用 Lua 脚本验证持有者身份并延长 TTL</span></span>
<span class="line"><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF"> (</span><span style="color: #D8DEE9">m</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">Mutex</span><span style="color: #D8DEE9FF">) </span><span style="color: #88C0D0">renew</span><span style="color: #D8DEE9FF">() </span><span style="color: #D8DEE9">error</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">ctx</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> cancel </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">context</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">WithTimeout</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">context</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Background</span><span style="color: #D8DEE9FF">()</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">renewTimeout</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">cancel</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">	script </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">`</span></span>
<span class="line"><span style="color: #A3BE8C">		if redis.call(&quot;GET&quot;, KEYS&#91;1&#93;) == ARGV&#91;1&#93; then</span></span>
<span class="line"><span style="color: #A3BE8C">			return redis.call(&quot;EXPIRE&quot;, KEYS&#91;1&#93;, ARGV&#91;2&#93;)</span></span>
<span class="line"><span style="color: #A3BE8C">		else</span></span>
<span class="line"><span style="color: #A3BE8C">			return 0</span></span>
<span class="line"><span style="color: #A3BE8C">		end</span></span>
<span class="line"><span style="color: #A3BE8C">	</span><span style="color: #ECEFF4">`</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">result</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Eval</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">script</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> []</span><span style="color: #D8DEE9">string</span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF">m.</span><span style="color: #D8DEE9">key</span><span style="color: #ECEFF4">},</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">value</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">int64</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">ttl</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Seconds</span><span style="color: #D8DEE9FF">()))</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Result</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">result</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">int64</span><span style="color: #D8DEE9FF">) </span><span style="color: #81A1C1">==</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">errors</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">New</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">lock not held or key missing</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// markLost 标记锁已丢失，由续期失败时调用</span></span>
<span class="line"><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF"> (</span><span style="color: #D8DEE9">m</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">Mutex</span><span style="color: #D8DEE9FF">) </span><span style="color: #88C0D0">markLost</span><span style="color: #D8DEE9FF">() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">mu</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Lock</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">mu</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Unlock</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">lost</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">true</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">locked</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">false</span><span style="color: #D8DEE9FF"> </span><span style="color: #616E88">// 丢失后即认为不再持有锁</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// stopWatchdog 停止看门狗协程并等待其退出</span></span>
<span class="line"><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF"> (</span><span style="color: #D8DEE9">m</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">Mutex</span><span style="color: #D8DEE9FF">) </span><span style="color: #88C0D0">stopWatchdog</span><span style="color: #D8DEE9FF">() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #ECEFF4">	</span><span style="color: #616E88">// 关闭 stopCh 通知看门狗退出（使用 select 避免重复关闭）</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">select</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">case</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&lt;-</span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">stopCh</span><span style="color: #D8DEE9FF">:</span></span>
<span class="line"><span style="color: #ECEFF4">		</span><span style="color: #616E88">// 已经关闭</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">default</span><span style="color: #D8DEE9FF">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #88C0D0">close</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">stopCh</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">	</span><span style="color: #616E88">// 等待看门狗协程完全退出</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #D8DEE9">m</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">wg</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Wait</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span></code></pre></div>



<p class="wp-block-paragraph">完整测试用例：</p>



<p class="wp-block-paragraph">测试依赖&nbsp;<code>miniredis</code>&nbsp;模拟 Redis 服务，请先安装：</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(1 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>go get github.com/alicebob/miniredis/v2</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #D8DEE9">go</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">get</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">github</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">com</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9">alicebob</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9">miniredis</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9">v2</span></span></code></pre></div>



<p class="wp-block-paragraph">测试文件：<code>mutex_test.go</code></p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(3 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>package redislock

import (
    "context"
    "sync"
    "testing"
    "time"

    "github.com/alicebob/miniredis/v2"
    "github.com/redis/go-redis/v9"
    "github.com/stretchr/testify/assert"
    "github.com/stretchr/testify/require"
)

// 创建测试用的 Redis 客户端（基于 miniredis）
func setupTestRedis(t *testing.T) (*redis.Client, *miniredis.Miniredis) {
    mr, err := miniredis.Run()
    require.NoError(t, err)

    client := redis.NewClient(&amp;redis.Options{
        Addr: mr.Addr(),
    })
    return client, mr
}

// 测试：正常加锁和解锁流程
func TestMutex_LockUnlock(t *testing.T) {
    client, mr := setupTestRedis(t)
    defer mr.Close()
    defer client.Close()

    mutex := NewMutex(client, "test:lock", 5*time.Second)
    ctx := context.Background()

    // 加锁
    err := mutex.Lock(ctx)
    require.NoError(t, err)

    // 验证 Redis 中确实存在该 key
    val, err := client.Get(ctx, "test:lock").Result()
    require.NoError(t, err)
    assert.Equal(t, mutex.value, val)

    // 解锁
    err = mutex.Unlock(ctx)
    require.NoError(t, err)

    // 验证 key 已被删除
    _, err = client.Get(ctx, "test:lock").Result()
    assert.ErrorIs(t, err, redis.Nil)
}

// 测试：TryLock 非阻塞获取
func TestMutex_TryLock(t *testing.T) {
    client, mr := setupTestRedis(t)
    defer mr.Close()
    defer client.Close()

    mutex1 := NewMutex(client, "test:trylock", 5*time.Second)
    mutex2 := NewMutex(client, "test:trylock", 5*time.Second)

    ctx := context.Background()

    // 第一个实例获取锁应成功
    ok, err := mutex1.TryLock(ctx)
    require.NoError(t, err)
    assert.True(t, ok)

    // 第二个实例获取锁应失败（已被占用）
    ok, err = mutex2.TryLock(ctx)
    require.NoError(t, err)
    assert.False(t, ok)

    // 释放锁后，第二个实例应能获取
    err = mutex1.Unlock(ctx)
    require.NoError(t, err)

    ok, err = mutex2.TryLock(ctx)
    require.NoError(t, err)
    assert.True(t, ok)
}

// 测试：阻塞等待超时
func TestMutex_LockTimeout(t *testing.T) {
    client, mr := setupTestRedis(t)
    defer mr.Close()
    defer client.Close()

    // 先占用锁
    holder := NewMutex(client, "test:timeout", 10*time.Second)
    err := holder.TryLock(context.Background())
    require.NoError(t, err)
    defer holder.Unlock(context.Background())

    // 第二个实例尝试获取，设置较短超时
    waiter := NewMutex(client, "test:timeout", 10*time.Second)
    ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
    defer cancel()

    start := time.Now()
    err = waiter.Lock(ctx)
    elapsed := time.Since(start)

    assert.Error(t, err)
    assert.Contains(t, err.Error(), "acquire timeout")
    assert.True(t, elapsed >= 500*time.Millisecond)
    assert.True(t, elapsed &lt; 1*time.Second)
}

// 测试：看门狗自动续期
func TestMutex_WatchdogRenew(t *testing.T) {
    client, mr := setupTestRedis(t)
    defer mr.Close()
    defer client.Close()

    // 设置较短的 TTL 和续期间隔，加快测试
    mutex := NewMutex(
        client,
        "test:watchdog",
        2*time.Second,
        WithMinRenewInterval(200*time.Millisecond),
    )

    ctx := context.Background()
    err := mutex.Lock(ctx)
    require.NoError(t, err)

    // 快进 Redis 时间，模拟 3 秒过去（超过原始 TTL）
    mr.FastForward(3 * time.Second)

    // 锁应该仍然存在（被看门狗续期）
    val, err := client.Get(ctx, "test:watchdog").Result()
    require.NoError(t, err)
    assert.Equal(t, mutex.value, val)

    // 释放锁
    err = mutex.Unlock(ctx)
    require.NoError(t, err)
}

// 测试：续期失败回调
func TestMutex_RenewFailedCallback(t *testing.T) {
    client, mr := setupTestRedis(t)
    defer mr.Close()
    defer client.Close()

    callbackCalled := make(chan error, 1)
    mutex := NewMutex(
        client,
        "test:callback",
        2*time.Second,
        WithMinRenewInterval(100*time.Millisecond),
        WithRenewFailedCallback(func(key string, err error) {
            callbackCalled &lt;- err
        }),
    )

    ctx := context.Background()
    err := mutex.Lock(ctx)
    require.NoError(t, err)

    // 模拟锁被外部删除（如手动 DEL）
    client.Del(ctx, "test:callback")

    // 等待回调被触发（续期时会发现锁不存在）
    select {
    case err := &lt;-callbackCalled:
        assert.Error(t, err)
    case &lt;-time.After(2 * time.Second):
        t.Fatal("续期失败回调未被调用")
    }

    // 解锁时应返回 ErrLockLost
    err = mutex.Unlock(ctx)
    assert.ErrorIs(t, err, ErrLockLost)
}

// 测试：锁持有者身份校验（防止误解锁）
func TestMutex_IdentityProtection(t *testing.T) {
    client, mr := setupTestRedis(t)
    defer mr.Close()
    defer client.Close()

    mutex1 := NewMutex(client, "test:identity", 5*time.Second)
    mutex2 := NewMutex(client, "test:identity", 5*time.Second)

    ctx := context.Background()

    // mutex1 加锁
    err := mutex1.Lock(ctx)
    require.NoError(t, err)

    // mutex2 尝试解锁应失败
    err = mutex2.Unlock(ctx)
    assert.ErrorIs(t, err, ErrLockNotHeld)

    // mutex1 解锁应成功
    err = mutex1.Unlock(ctx)
    assert.NoError(t, err)
}

// 测试：IsHeld 实时状态查询
func TestMutex_IsHeld(t *testing.T) {
    client, mr := setupTestRedis(t)
    defer mr.Close()
    defer client.Close()

    mutex := NewMutex(client, "test:isheld", 5*time.Second)
    ctx := context.Background()

    // 加锁前
    held, err := mutex.IsHeld(ctx)
    require.NoError(t, err)
    assert.False(t, held)

    // 加锁后
    err = mutex.Lock(ctx)
    require.NoError(t, err)
    held, err = mutex.IsHeld(ctx)
    require.NoError(t, err)
    assert.True(t, held)

    // 解锁后
    err = mutex.Unlock(ctx)
    require.NoError(t, err)
    held, err = mutex.IsHeld(ctx)
    require.NoError(t, err)
    assert.False(t, held)
}

// 测试：并发竞争场景
func TestMutex_Concurrent(t *testing.T) {
    client, mr := setupTestRedis(t)
    defer mr.Close()
    defer client.Close()

    const goroutines = 20
    var wg sync.WaitGroup
    counter := 0
    var mu sync.Mutex // 仅用于保护 counter 的本地累加

    for i := 0; i &lt; goroutines; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()

            mutex := NewMutex(client, "test:concurrent", 5*time.Second)
            ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
            defer cancel()

            if err := mutex.Lock(ctx); err != nil {
                t.Logf("goroutine 获取锁失败: %v", err)
                return
            }
            defer mutex.Unlock(context.Background())

            // 临界区：安全地修改共享资源
            mu.Lock()
            counter++
            mu.Unlock()

            // 模拟业务处理
            time.Sleep(50 * time.Millisecond)
        }()
    }

    wg.Wait()

    // 最终 counter 应等于成功获取锁的 goroutine 数量（即 goroutines）
    assert.Equal(t, goroutines, counter)
}

// 测试：指数退避重试策略
func TestMutex_ExponentialBackoff(t *testing.T) {
    client, mr := setupTestRedis(t)
    defer mr.Close()
    defer client.Close()

    // 先占用锁
    holder := NewMutex(client, "test:backoff", 10*time.Second)
    err := holder.TryLock(context.Background())
    require.NoError(t, err)
    defer holder.Unlock(context.Background())

    // 第二个实例使用指数退避
    waiter := NewMutex(
        client,
        "test:backoff",
        10*time.Second,
        WithExponentialBackoff(50*time.Millisecond, 1*time.Second),
    )

    ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
    defer cancel()

    start := time.Now()
    err = waiter.Lock(ctx)
    elapsed := time.Since(start)

    assert.Error(t, err)
    // 验证确实等待了一段时间（由于退避，总等待时间应大于最小间隔）
    assert.True(t, elapsed > 100*time.Millisecond)
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">package</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">redislock</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">import</span><span style="color: #D8DEE9FF"> (</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">context</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">sync</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">testing</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">time</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">github.com/alicebob/miniredis/v2</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">github.com/redis/go-redis/v9</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">github.com/stretchr/testify/assert</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">github.com/stretchr/testify/require</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// 创建测试用的 Redis 客户端（基于 miniredis）</span></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">setupTestRedis</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">testing</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">T</span><span style="color: #D8DEE9FF">) (</span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">redis</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Client</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">miniredis</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Miniredis</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">mr</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">miniredis</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Run</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">require</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">NoError</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    client </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">redis</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">NewClient</span><span style="color: #D8DEE9FF">(</span><span style="color: #81A1C1">&amp;</span><span style="color: #D8DEE9">redis</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Options</span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #88C0D0">Addr</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">mr</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Addr</span><span style="color: #D8DEE9FF">()</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">mr</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// 测试：正常加锁和解锁流程</span></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">TestMutex_LockUnlock</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">testing</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">T</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> mr </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">setupTestRedis</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">mr</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Close</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Close</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    mutex </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">NewMutex</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">test:lock</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">5</span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Second</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    ctx </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">context</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Background</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 加锁</span></span>
<span class="line"><span style="color: #D8DEE9FF">    err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">mutex</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Lock</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">require</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">NoError</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 验证 Redis 中确实存在该 key</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">val</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Get</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">test:lock</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Result</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">require</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">NoError</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">assert</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Equal</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">mutex</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">value</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">val</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 解锁</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">mutex</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Unlock</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">require</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">NoError</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 验证 key 已被删除</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">_</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Get</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">test:lock</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Result</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">assert</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">ErrorIs</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">redis</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Nil</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// 测试：TryLock 非阻塞获取</span></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">TestMutex_TryLock</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">testing</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">T</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> mr </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">setupTestRedis</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">mr</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Close</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Close</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    mutex1 </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">NewMutex</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">test:trylock</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">5</span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Second</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    mutex2 </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">NewMutex</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">test:trylock</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">5</span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Second</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    ctx </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">context</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Background</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 第一个实例获取锁应成功</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">ok</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">mutex1</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">TryLock</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">require</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">NoError</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">assert</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">True</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">ok</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 第二个实例获取锁应失败（已被占用）</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">ok</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">mutex2</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">TryLock</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">require</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">NoError</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">assert</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">False</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">ok</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 释放锁后，第二个实例应能获取</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">mutex1</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Unlock</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">require</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">NoError</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">ok</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">mutex2</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">TryLock</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">require</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">NoError</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">assert</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">True</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">ok</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// 测试：阻塞等待超时</span></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">TestMutex_LockTimeout</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">testing</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">T</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> mr </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">setupTestRedis</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">mr</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Close</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Close</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 先占用锁</span></span>
<span class="line"><span style="color: #D8DEE9FF">    holder </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">NewMutex</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">test:timeout</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">10</span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Second</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">holder</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">TryLock</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">context</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Background</span><span style="color: #D8DEE9FF">())</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">require</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">NoError</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">holder</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Unlock</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">context</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Background</span><span style="color: #D8DEE9FF">())</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 第二个实例尝试获取，设置较短超时</span></span>
<span class="line"><span style="color: #D8DEE9FF">    waiter </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">NewMutex</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">test:timeout</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">10</span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Second</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">ctx</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> cancel </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">context</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">WithTimeout</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">context</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Background</span><span style="color: #D8DEE9FF">()</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">500</span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Millisecond</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">cancel</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    start </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Now</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">waiter</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Lock</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    elapsed </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Since</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">start</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">assert</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Error</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">assert</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Contains</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Error</span><span style="color: #D8DEE9FF">()</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">acquire timeout</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">assert</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">True</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">elapsed</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&gt;=</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">500</span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Millisecond</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">assert</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">True</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">elapsed</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1</span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Second</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// 测试：看门狗自动续期</span></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">TestMutex_WatchdogRenew</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">testing</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">T</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> mr </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">setupTestRedis</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">mr</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Close</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Close</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 设置较短的 TTL 和续期间隔，加快测试</span></span>
<span class="line"><span style="color: #D8DEE9FF">    mutex </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">NewMutex</span><span style="color: #D8DEE9FF">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">test:watchdog</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #B48EAD">2</span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Second</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #88C0D0">WithMinRenewInterval</span><span style="color: #D8DEE9FF">(</span><span style="color: #B48EAD">200</span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Millisecond</span><span style="color: #D8DEE9FF">)</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    )</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    ctx </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">context</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Background</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">mutex</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Lock</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">require</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">NoError</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 快进 Redis 时间，模拟 3 秒过去（超过原始 TTL）</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">mr</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">FastForward</span><span style="color: #D8DEE9FF">(</span><span style="color: #B48EAD">3</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Second</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 锁应该仍然存在（被看门狗续期）</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">val</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Get</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">test:watchdog</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Result</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">require</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">NoError</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">assert</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Equal</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">mutex</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">value</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">val</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 释放锁</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">mutex</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Unlock</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">require</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">NoError</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// 测试：续期失败回调</span></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">TestMutex_RenewFailedCallback</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">testing</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">T</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> mr </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">setupTestRedis</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">mr</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Close</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Close</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    callbackCalled </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">make</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">chan</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">error</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    mutex </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">NewMutex</span><span style="color: #D8DEE9FF">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">test:callback</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #B48EAD">2</span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Second</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #88C0D0">WithMinRenewInterval</span><span style="color: #D8DEE9FF">(</span><span style="color: #B48EAD">100</span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Millisecond</span><span style="color: #D8DEE9FF">)</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #88C0D0">WithRenewFailedCallback</span><span style="color: #D8DEE9FF">(</span><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">key</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">string</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">error</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            callbackCalled &lt;- </span><span style="color: #D8DEE9">err</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF">)</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    )</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    ctx </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">context</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Background</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">mutex</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Lock</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">require</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">NoError</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 模拟锁被外部删除（如手动 DEL）</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Del</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">test:callback</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 等待回调被触发（续期时会发现锁不存在）</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">select</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">case</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&lt;-</span><span style="color: #D8DEE9FF">callbackCalled</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">assert</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Error</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">case</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&lt;-</span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">After</span><span style="color: #D8DEE9FF">(</span><span style="color: #B48EAD">2</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Second</span><span style="color: #D8DEE9FF">):</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Fatal</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">续期失败回调未被调用</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 解锁时应返回 ErrLockLost</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">mutex</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Unlock</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">assert</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">ErrorIs</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">ErrLockLost</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// 测试：锁持有者身份校验（防止误解锁）</span></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">TestMutex_IdentityProtection</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">testing</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">T</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> mr </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">setupTestRedis</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">mr</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Close</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Close</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    mutex1 </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">NewMutex</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">test:identity</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">5</span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Second</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    mutex2 </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">NewMutex</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">test:identity</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">5</span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Second</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    ctx </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">context</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Background</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// mutex1 加锁</span></span>
<span class="line"><span style="color: #D8DEE9FF">    err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">mutex1</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Lock</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">require</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">NoError</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// mutex2 尝试解锁应失败</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">mutex2</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Unlock</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">assert</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">ErrorIs</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">ErrLockNotHeld</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// mutex1 解锁应成功</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">mutex1</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Unlock</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">assert</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">NoError</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// 测试：IsHeld 实时状态查询</span></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">TestMutex_IsHeld</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">testing</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">T</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> mr </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">setupTestRedis</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">mr</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Close</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Close</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    mutex </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">NewMutex</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">test:isheld</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">5</span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Second</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    ctx </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">context</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Background</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 加锁前</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">held</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">mutex</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">IsHeld</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">require</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">NoError</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">assert</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">False</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">held</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 加锁后</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">mutex</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Lock</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">require</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">NoError</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">held</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">mutex</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">IsHeld</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">require</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">NoError</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">assert</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">True</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">held</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 解锁后</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">mutex</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Unlock</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">require</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">NoError</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">held</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">mutex</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">IsHeld</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">require</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">NoError</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">assert</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">False</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">held</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// 测试：并发竞争场景</span></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">TestMutex_Concurrent</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">testing</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">T</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> mr </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">setupTestRedis</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">mr</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Close</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Close</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">const</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">goroutines</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">20</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">var</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">wg</span><span style="color: #D8DEE9FF"> sync.WaitGroup</span></span>
<span class="line"><span style="color: #D8DEE9FF">    counter </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">var</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">mu</span><span style="color: #D8DEE9FF"> sync.Mutex </span><span style="color: #616E88">// 仅用于保护 counter 的本地累加</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">for</span><span style="color: #D8DEE9FF"> i </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">i</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">goroutines</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">i</span><span style="color: #81A1C1">++</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">wg</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Add</span><span style="color: #D8DEE9FF">(</span><span style="color: #B48EAD">1</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">go</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">func</span><span style="color: #D8DEE9FF">() </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">wg</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Done</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">            mutex </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">NewMutex</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">test:concurrent</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">5</span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Second</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #D8DEE9">ctx</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> cancel </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">context</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">WithTimeout</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">context</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Background</span><span style="color: #D8DEE9FF">()</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">10</span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Second</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">cancel</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">mutex</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Lock</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #D8DEE9FF">)</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Logf</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">goroutine 获取锁失败: %v</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #81A1C1">return</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">mutex</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Unlock</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">context</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Background</span><span style="color: #D8DEE9FF">())</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">            </span><span style="color: #616E88">// 临界区：安全地修改共享资源</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #D8DEE9">mu</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Lock</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #D8DEE9">counter</span><span style="color: #81A1C1">++</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #D8DEE9">mu</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Unlock</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">            </span><span style="color: #616E88">// 模拟业务处理</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Sleep</span><span style="color: #D8DEE9FF">(</span><span style="color: #B48EAD">50</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Millisecond</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">wg</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Wait</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 最终 counter 应等于成功获取锁的 goroutine 数量（即 goroutines）</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">assert</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Equal</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">goroutines</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">counter</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// 测试：指数退避重试策略</span></span>
<span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">TestMutex_ExponentialBackoff</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">testing</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">T</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> mr </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">setupTestRedis</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">mr</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Close</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Close</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 先占用锁</span></span>
<span class="line"><span style="color: #D8DEE9FF">    holder </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">NewMutex</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">test:backoff</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">10</span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Second</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    err </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">holder</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">TryLock</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">context</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Background</span><span style="color: #D8DEE9FF">())</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">require</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">NoError</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">holder</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Unlock</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">context</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Background</span><span style="color: #D8DEE9FF">())</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 第二个实例使用指数退避</span></span>
<span class="line"><span style="color: #D8DEE9FF">    waiter </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">NewMutex</span><span style="color: #D8DEE9FF">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">client</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">test:backoff</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #B48EAD">10</span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Second</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #88C0D0">WithExponentialBackoff</span><span style="color: #D8DEE9FF">(</span><span style="color: #B48EAD">50</span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Millisecond</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1</span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Second</span><span style="color: #D8DEE9FF">)</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    )</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">ctx</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> cancel </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">context</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">WithTimeout</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">context</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Background</span><span style="color: #D8DEE9FF">()</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">500</span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Millisecond</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">defer</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">cancel</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    start </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Now</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">waiter</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Lock</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ctx</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    elapsed </span><span style="color: #ECEFF4">:</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Since</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">start</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">assert</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">Error</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">err</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// 验证确实等待了一段时间（由于退避，总等待时间应大于最小间隔）</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">assert</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">True</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">elapsed</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">100</span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9">time</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">Millisecond</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span></code></pre></div>



<h2 class="wp-block-heading">运行测试</h2>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(1 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>go test -v -race ./...</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #D8DEE9">go</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">test</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9">v</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9">race</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">.</span><span style="color: #81A1C1">/...</span></span></code></pre></div>



<p class="wp-block-paragraph"><code>-race</code>&nbsp;标志会启用数据竞争检测，确保并发安全。</p>



<p class="wp-block-paragraph"><br>注意事项</p>



<ol start="1" class="wp-block-list">
<li><strong>miniredis 时间快进</strong>：<code>mr.FastForward()</code>&nbsp;会推进 Redis 内部时钟，用于模拟过期时间，这对测试看门狗续期非常有用。</li>



<li><strong>测试并发安全</strong>：使用&nbsp;<code>-race</code>&nbsp;运行测试可发现潜在的竞态条件。</li>



<li><strong>生产环境日志</strong>：示例中的&nbsp;<code>Logger</code>&nbsp;接口可以替换为&nbsp;<code>*slog.Logger</code>&nbsp;或&nbsp;<code>logrus</code>&nbsp;实现，方便集成。</li>



<li><strong>上下文传递</strong>：建议业务层在调用&nbsp;<code>Lock</code>&nbsp;时传入带有&nbsp;<code>trace_id</code>&nbsp;的 context，便于问题追踪。</li>
</ol>
<p>The post <a href="https://www.atomic-cube.cn/%e4%bd%bf%e7%94%a8go%e8%af%ad%e8%a8%80%e5%ae%9e%e7%8e%b0redis%e5%88%86%e5%b8%83%e5%bc%8f%e9%94%81%ef%bc%88%e9%99%84%e6%9c%89%e7%9c%8b%e9%97%a8%e7%8b%97%e8%87%aa%e5%8a%a8%e7%bb%ad%e6%9c%9f%e6%9c%ba/">使用Go语言实现Redis分布式锁（附有看门狗自动续期机制）</a> appeared first on <a href="https://www.atomic-cube.cn">原立方</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.atomic-cube.cn/%e4%bd%bf%e7%94%a8go%e8%af%ad%e8%a8%80%e5%ae%9e%e7%8e%b0redis%e5%88%86%e5%b8%83%e5%bc%8f%e9%94%81%ef%bc%88%e9%99%84%e6%9c%89%e7%9c%8b%e9%97%a8%e7%8b%97%e8%87%aa%e5%8a%a8%e7%bb%ad%e6%9c%9f%e6%9c%ba/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
