以下问题: 我有一个只允许一个调用者执行的函数。 如果有人试图调用该函数并且它已经忙,则第二个调用者应该立即返回错误。
我尝试了以下内容:
1。使用互斥锁
会很容易的。但问题是,您无法检查互斥锁是否被锁定。你只能阻止它。因此它不起作用
2。等待频道
var canExec = make(chan bool, 1)
func init() {
canExec <- true
}
func onlyOne() error {
select {
case <-canExec:
default:
return errors.New("already busy")
}
defer func() {
fmt.Println("done")
canExec <- true
}()
// do stuff
}
我不喜欢这里:
第3。互斥和混合状态的混合
var open = true
var myMutex *sync.Mutex
func canExec() bool {
myMutex.Lock()
defer myMutex.Unlock()
if open {
open = false
return true
}
return false
}
func endExec() {
myMutex.Lock()
defer myMutex.Unlock()
open = true
}
func onlyOne() error {
if !canExec() {
return errors.New("busy")
}
defer endExec()
// do stuff
return nil
}
我也不喜欢这样。使用带有互斥锁的分片变量并不是那么好。
还有其他想法吗?
答案 0 :(得分:14)
我会抛弃我的偏好 - 使用atomic package。
var (
locker uint32
errLocked = errors.New("Locked out buddy")
)
func OneAtATime(d time.Duration) error {
if !atomic.CompareAndSwapUint32(&locker, 0, 1) { // <-----------------------------
return errLocked // All logic in these |
} // four lines |
defer atomic.StoreUint32(&locker, 0) // <-----------------------------
// logic here, but we will sleep
time.Sleep(d)
return nil
}
这个想法非常简单。将初始值设置为0(uint32的0值)。你在函数中做的第一件事是检查locker
的值当前是否为0,如果是,则将其更改为1.它以原子方式执行所有操作。如果失败则只返回错误(或者你喜欢处理锁定状态)。如果成功,您立即将值(现在为1)更改为0.您不必显然使用延迟,但在返回之前未将值设置回0将使您处于函数无法执行的状态更长的时间。
完成这4行设置之后,你可以做任何你想做的事。
https://play.golang.org/p/riryVJM4Qf
如果需要,可以通过为州使用命名值来使事情变得更好。
const (
stateUnlocked uint32 = iota
stateLocked
)
var (
locker = stateUnlocked
errLocked = errors.New("Locked out buddy")
)
func OneAtATime(d time.Duration) error {
if !atomic.CompareAndSwapUint32(&locker, stateUnlocked, stateLocked) {
return errLocked
}
defer atomic.StoreUint32(&locker, stateUnlocked)
// logic here, but we will sleep
time.Sleep(d)
return nil
}
答案 1 :(得分:4)
您可以使用标准通道方法和select语句。
var (
ch = make(chan bool)
)
func main() {
i := 0
wg := sync.WaitGroup{}
for i < 100 {
i++
wg.Add(1)
go func() {
defer wg.Done()
err := onlyOne()
if err != nil {
fmt.Println("Error: ", err)
} else {
fmt.Println("Ok")
}
}()
go func() {
ch <- true
}()
}
wg.Wait()
}
func onlyOne() error {
select {
case <-ch:
// do stuff
return nil
default:
return errors.New("Busy")
}
}
答案 2 :(得分:2)
您可以使用信号量(go get golang.org/x/sync/semaphore
)
package main
import (
"errors"
"fmt"
"sync"
"time"
"golang.org/x/sync/semaphore"
)
var sem = semaphore.NewWeighted(1)
func main() {
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
if err := onlyOne(); err != nil {
fmt.Println(err)
}
}()
time.Sleep(time.Second)
}
wg.Wait()
}
func onlyOne() error {
if !sem.TryAcquire(1) {
return errors.New("busy")
}
defer sem.Release(1)
fmt.Println("working")
time.Sleep(5 * time.Second)
return nil
}
答案 3 :(得分:1)
您希望函数在给定时间执行一次或一次吗?在前一种情况下,请查看https://golang.org/pkg/sync/#Once。
如果你想要一次解决一次:
package main
import (
"fmt"
"sync"
"time"
)
// OnceAtATime protects function from being executed simultaneously.
// Example:
// func myFunc() { time.Sleep(10*time.Second) }
// func main() {
// once := OnceAtATime{}
// once.Do(myFunc)
// once.Do(myFunc) // not executed
// }
type OnceAtATime struct {
m sync.Mutex
executed bool
}
func (o *OnceAtATime) Do(f func()) {
o.m.Lock()
if o.executed {
o.m.Unlock()
return
}
o.executed = true
o.m.Unlock()
f()
o.m.Lock()
o.executed = false
o.m.Unlock()
}
// Proof of concept
func f(m int, done chan<- struct{}) {
for i := 0; i < 10; i++ {
fmt.Printf("%d: %d\n", m, i)
time.Sleep(250 * time.Millisecond)
}
close(done)
}
func main() {
done := make(chan struct{})
once := OnceAtATime{}
go once.Do(func() { f(1, done) })
go once.Do(func() { f(2, done) })
<-done
done = make(chan struct{})
go once.Do(func() { f(3, done) })
<-done
}
答案 4 :(得分:1)
但问题是,您无法检查互斥锁是否已锁定。你只能阻止它。因此它不起作用
随着可能的 Go 1.18(2022 年第一季度),您将能够测试互斥锁是否被锁定......不会阻塞它。
看到(如mentioned by Go 101)来自issue 45435的Tye McQueen:
<块引用>sync
:添加Mutex.TryLock
后面跟着 CL 319769,注意:
<块引用>使用这些函数几乎(但并非)总是一个坏主意。
它们很少是必要的,并且第三方实现(例如使用互斥锁和原子词)无法像包同步本身中的实现那样与竞争检测器集成。
The objections(自撤回后)是:
<块引用>锁是用来保护不变量的。
如果锁是由其他人持有的,那么关于不变量你就无话可说了。
TryLock
鼓励对锁的不精确思考;它鼓励对可能正确也可能不正确的不变量做出假设。
这最终成为它自己的种族来源。
仔细考虑一下,与包装器相比,将 TryLock
构建到 Mutex
中有一个重要的好处:
失败的 TryLock
调用不会创建虚假的happens-before 边缘来混淆竞争检测器。
还有:
<块引用>基于通道的实现是可能的,但相比之下表现不佳。
我们使用 sync.Mutex
而不仅仅是使用通道进行锁定是有原因的。
答案 5 :(得分:0)
我想出了以下通用解决方案:
对我有用,或者你觉得有什么问题吗?
import (
"sync"
)
const (
ONLYONECALLER_LOCK = "onlyonecaller"
ANOTHER_LOCK = "onlyonecaller"
)
var locks = map[string]bool{}
var mutex = &sync.Mutex{}
func Lock(lock string) bool {
mutex.Lock()
defer mutex.Unlock()
locked, ok := locks[lock]
if !ok {
locks[lock] = true
return true
}
if locked {
return false
}
locks[lock] = true
return true
}
func IsLocked(lock string) bool {
mutex.Lock()
defer mutex.Unlock()
locked, ok := locks[lock]
if !ok {
return false
}
return locked
}
func Unlock(lock string) {
mutex.Lock()
defer mutex.Unlock()
locked, ok := locks[lock]
if !ok {
return
}
if !locked {
return
}
locks[lock] = false
}
答案 6 :(得分:0)
该软件包如何:https://github.com/viney-shih/go-lock。它使用频道和信号量(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
}
答案 7 :(得分:0)
让我们保持简单:
package main
import (
"fmt"
"time"
"golang.org/x/sync/semaphore"
)
var sem *semaphore.NewWeighted(1)
func init() {
sem = emaphore.NewWeighted(1)
}
func doSomething() {
if !sem.TryAcquire(1) {
return errors.New("I'm busy")
}
defer sem.Release(1)
fmt.Println("I'm doing my work right now, then I'll take a nap")
time.Sleep(10)
}
func main() {
go func() {
doSomething()
}()
}