试图在 golang 中获取具有截止日期的锁?

Attempting to acquire a lock with a deadline in golang?

如何才能在 go 中尝试获取类似互斥锁的锁,要么立即中止(就像 TryLock 在其他实现中所做的那样),要么通过观察某种形式的截止日期(基本上LockBefore)?

我现在可以想到 2 种情况,在这些情况下这会非常有帮助,并且我正在寻找某种解决方案。第一个是:CPU-heavy 服务,它接收对延迟敏感的请求(例如 Web 服务)。在这种情况下,您可能希望执行类似于下面的 RPCService 示例的操作。可以将它实现为一个工作队列(带有通道和东西),但在那种情况下,衡量和利用所有可用的东西变得更加困难 CPU。也可以只接受当你获得锁时你的代码可能已经超过截止日期,但这并不理想,因为它浪费了一些资源并且意味着我们不能做像 "degraded ad-hoc response" 这样的事情.

    /* Example 1: LockBefore() for latency sensitive code. */
    func (s *RPCService) DoTheThing(ctx context.Context, ...) ... {
      if s.someObj[req.Parameter].mtx.LockBefore(ctx.Deadline()) {
        defer s.someObj[req.Parameter].mtx.Unlock()
        ... expensive computation based on internal state ...
      } else {
        return s.cheapCachedResponse[req.Parameter]
      }
    }

另一种情况是当你有一堆应该被触摸但可能被锁定的对象时,触摸它们应该在一定时间内完成(例如更新一些统计数据)。在这种情况下,您也可以使用 LockBefore() 或某种形式的 TryLock(),请参阅下面的统计示例。

    /* Example 2: TryLock() for updating stats. */
    func (s *StatsObject) updateObjStats(key, value interface{}) {
      if s.someObj[key].TryLock() {
        defer s.someObj[key].Unlock()
        ... update stats ...
        ... fill in s.cheapCachedResponse ...
      }
    }

    func (s *StatsObject) UpdateStats() {
      s.someObj.Range(s.updateObjStats)
    }

为了便于使用,我们假设在上述情况下我们谈论的是相同的 s.someObj。任何对象都可能被 DoTheThing() 操作阻塞很长时间,这意味着我们希望在 updateObjStats 中跳过它。此外,我们希望确保我们 return DoTheThing() 中的廉价响应,以防我们无法及时获得锁定。

不幸的是,sync.Mutex 仅具有 Lock()Unlock() 功能。无法可能 获取锁。有没有一些简单的方法可以做到这一点?我是不是从一个完全错误的角度来处理这个 class 问题,是否有不同的、更 "go" 似的方法来解决它们?或者如果我想解决这些问题,我是否必须实现自己的 Mutex 库?我知道 issue 6123 这似乎表明没有这样的事情,而且我处理这些问题的方式完全不合时宜。

我认为你在这里问了几个不同的问题:

  1. 标准库中是否存在此功能?不,它没有。您可能可以在其他地方找到实现 - 这可以使用 使用 标准库(例如原子)来实现。

  2. 为什么标准库中没有这个功能:你在问题中提到的问题是一个讨论。在 go-nuts 邮件列表上也有一些讨论,其中有几位 Go 代码开发人员做出了贡献:link 1, link 2。而且很容易通过谷歌搜索找到其他讨论。

  3. 我怎样才能设计我的程序而不需要这个?

(3) 的答案更加微妙,具体取决于您的具体问题。你的问题已经说了

It is possible to implement it as a worker queue (with channels and stuff), but in that case it becomes more difficult to gauge and utilize all available CPU

没有详细说明为什么使用所有 CPU 比检查互斥锁状态更困难。

在 Go 中,只要锁定方案变得重要,您通常就需要通道。它不应该更慢,而且应该更易于维护。

使用缓冲区大小为 1 的通道作为互斥量。

l := make(chan struct{}, 1)

锁定:

l <- struct{}{}

解锁:

<-l

尝试锁定:

select {
case l <- struct{}{}:
    // lock acquired
    <-l
default:
    // lock not acquired
}

尝试超时:

select {
case l <- struct{}{}:
    // lock acquired
    <-l
case <-time.After(time.Minute):
    // lock not acquired
}

这个包怎么样:https://github.com/viney-shih/go-lock . It use channel and semaphore (golang.org/x/sync/semaphore)来解决你的问题。

go-lock 除了锁定和解锁之外还实现了 TryLockTryLockWithTimeoutTryLockWithContext 功能。它提供了控制资源的灵活性。

示例:

package main

import (
    "fmt"
    "time"
    "context"

    lock "github.com/viney-shih/go-lock"
)

func main() {
    casMut := lock.NewCASMutex()

    casMut.Lock()
    defer casMut.Unlock()

    // TryLock without blocking
    fmt.Println("Return", casMut.TryLock()) // Return false

    // TryLockWithTimeout without blocking
    fmt.Println("Return", casMut.TryLockWithTimeout(50*time.Millisecond)) // Return false

    // TryLockWithContext without blocking
    ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
    defer cancel()

    fmt.Println("Return", casMut.TryLockWithContext(ctx)) // Return false


    // Output:
    // Return false
    // Return false
    // Return false
}

软件包 https://github.com/myfantasy/mfs

中的 PMutex

PMutex 实现了 RTryLock(ctx context.Context) 和 TryLock(ctx context.Context)

// ctx - some context
ctx := context.Background()
mx := mfs.PMutex{}
isLocked := mx.TryLock(ctx)
if isLocked {
    // DO Something
    mx.Unlock()
} else {
    // DO Something else
}