试图在 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 这似乎表明没有这样的事情,而且我处理这些问题的方式完全不合时宜。
我认为你在这里问了几个不同的问题:
标准库中是否存在此功能?不,它没有。您可能可以在其他地方找到实现 - 这可以使用 使用 标准库(例如原子)来实现。
为什么标准库中没有这个功能:你在问题中提到的问题是一个讨论。在 go-nuts 邮件列表上也有一些讨论,其中有几位 Go 代码开发人员做出了贡献:link 1, link 2。而且很容易通过谷歌搜索找到其他讨论。
我怎样才能设计我的程序而不需要这个?
(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
除了锁定和解锁之外还实现了 TryLock
、TryLockWithTimeout
和 TryLockWithContext
功能。它提供了控制资源的灵活性。
示例:
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
}
如何才能在 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 这似乎表明没有这样的事情,而且我处理这些问题的方式完全不合时宜。
我认为你在这里问了几个不同的问题:
标准库中是否存在此功能?不,它没有。您可能可以在其他地方找到实现 - 这可以使用 使用 标准库(例如原子)来实现。
为什么标准库中没有这个功能:你在问题中提到的问题是一个讨论。在 go-nuts 邮件列表上也有一些讨论,其中有几位 Go 代码开发人员做出了贡献:link 1, link 2。而且很容易通过谷歌搜索找到其他讨论。
我怎样才能设计我的程序而不需要这个?
(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
除了锁定和解锁之外还实现了 TryLock
、TryLockWithTimeout
和 TryLockWithContext
功能。它提供了控制资源的灵活性。
示例:
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
中的 PMutexPMutex 实现了 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
}