以下问题:我有一个只允许一个调用者执行的函数.如果有人试图调用该函数并且它已经忙,则第二个调用者应该立即返回错误.
我尝试了以下方法:
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
}
Run Code Online (Sandbox Code Playgroud)
我不喜欢这里:
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
}
Run Code Online (Sandbox Code Playgroud)
我也不喜欢这个.使用带有互斥锁的分片变量并不是那么好.
还有其他想法吗?
sbe*_*rry 17
我会抛弃我的偏好 - 使用原子包.
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
}
Run Code Online (Sandbox Code Playgroud)
这个想法非常简单.将初始值设置为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
}
Run Code Online (Sandbox Code Playgroud)
您可以为此使用信号量(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
}
Run Code Online (Sandbox Code Playgroud)