英文:
Good way to return on locked mutex in go
问题
以下是翻译的内容:
以下问题:
我有一个函数,只允许一个调用者执行。
如果有人尝试调用该函数,而且它已经在忙碌中,第二个调用者应立即返回错误。
我尝试了以下方法:
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
}()
// 做一些事情
}
我不喜欢这个方法的原因:
- 看起来很混乱
- 很容易错误地在通道上阻塞/错误地向通道写入数据
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()
// 做一些事情
return nil
}
我也不喜欢这个方法。使用带有互斥锁的共享变量不太好。
还有其他的想法吗?
英文:
Following problem:
I have a function that only should allow one caller to execute.
If someone tries to call the function and it is already busy the second caller should immediatly return with an error.
I tried the following:
1. Use a mutex
Would be pretty easy. But the problem is, you cannot check if a mutex is locked. You can only block on it. Therefore it does not work
2. Wait on a channel
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
}
What I don't like here:
- looks really messi
- if easy to mistakenly block on the channel / mistakenly write to the channel
3. Mixture of mutex and shared state
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
}
I don't like this either. Using a shard variable with mutex is not that nice.
Any other idea?
答案1
得分: 36
我会把你提供的内容翻译成中文,以下是翻译的结果:
我会提出我的偏好 - 使用atomic包。
var (
locker uint32
errLocked = errors.New("被锁住了伙计")
)
func OneAtATime(d time.Duration) error {
if !atomic.CompareAndSwapUint32(&locker, 0, 1) { // <-----------------------------
return errLocked // 所有逻辑都在这四行里面 |
} // 这四行代码 |
defer atomic.StoreUint32(&locker, 0) // <-----------------------------
// 在这里进行逻辑处理,但是我们会休眠
time.Sleep(d)
return nil
}
这个想法非常简单。将初始值设置为0(uint32的零值)。函数中的第一件事是检查locker
的值是否为0,如果是,则将其更改为1。所有这些操作都是原子性的。如果失败,简单地返回一个错误(或者以其他方式处理被锁定的状态)。如果成功,立即延迟将值(现在为1)替换为0。当然你不一定非要使用defer,但是如果在返回之前不将值设置回0,那么函数将无法再次运行。
在完成这4行设置之后,你可以按照正常的方式进行其他操作。
你可以通过为状态使用命名值来使代码更加清晰。
const (
stateUnlocked uint32 = iota
stateLocked
)
var (
locker = stateUnlocked
errLocked = errors.New("被锁住了伙计")
)
func OneAtATime(d time.Duration) error {
if !atomic.CompareAndSwapUint32(&locker, stateUnlocked, stateLocked) {
return errLocked
}
defer atomic.StoreUint32(&locker, stateUnlocked)
// 在这里进行逻辑处理,但是我们会休眠
time.Sleep(d)
return nil
}
英文:
I'll throw my preference out there - use the 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
}
The idea is pretty simple. Set the initial value to 0 (0 value of uint32). The first thing you do in the function is check if the value of locker
is currently 0 and if so it changes it to 1. It does all of this atomically. If it fails simply return an error (or however else you like to handle a locked state). If successful, you immediately defer replacing the value (now 1) with 0. You don't have to use defer obviously, but failing to set the value back to 0 before returning would leave you in a state where the function could no longer be run.
After you do those 4 lines of setup, you do whatever you would normally.
https://play.golang.org/p/riryVJM4Qf
You can make things a little nicer if desired by using named values for your states.
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
}
答案2
得分: 31
你可以使用信号量来实现这个(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
}
希望对你有帮助!
英文:
You can use a semaphore for this (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
得分: 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("错误:", err)
} else {
fmt.Println("好的")
}
}()
go func() {
ch <- true
}()
}
wg.Wait()
}
func onlyOne() error {
select {
case <-ch:
// 做一些事情
return nil
default:
return errors.New("忙碌")
}
}
请注意,我只翻译了代码部分,其他内容被忽略了。
英文:
You could use standard channel approach with select statement.
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")
}
}
答案4
得分: 4
你想要一个函数只执行一次,还是在给定的时间内执行一次?如果是前者,请参考https://golang.org/pkg/sync/#Once。
如果你想要一次执行一个解决方案:
package main
import (
"fmt"
"sync"
"time"
)
// OnceAtATime 保护函数不被同时执行。
// 示例:
// func myFunc() { time.Sleep(10*time.Second) }
// func main() {
// once := OnceAtATime{}
// once.Do(myFunc)
// once.Do(myFunc) // 不会执行
// }
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
}
链接:https://play.golang.org/p/nZcEcWAgKp
英文:
Do you want a function to be executed exactly once or once at given time? In former case take a look at https://golang.org/pkg/sync/#Once.
If you want once at a time solution:
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
}
答案5
得分: 2
但问题是,你无法检查互斥锁是否被锁定。你只能在其上阻塞。因此,它不起作用。
可能在 Go 1.18(2022年第一季度)中,你将能够测试互斥锁是否被锁定...而不会阻塞。
参考 Go 101 提到的 issue 45435,由 Tye McQueen 提出:
sync
: 添加 Mutex.TryLock
随后是 CL 319769,但有一个警告:
这些函数的使用几乎总是一个坏主意(但不总是)。
很少情况下它们是必要的,并且第三方实现(例如使用互斥锁和原子操作)无法像 sync
包本身的实现那样与竞争检测器集成。
最初的反对意见(已经撤回)是:
锁用于保护不变量。
如果锁被其他人持有,你对不变量无法做出任何断言。
TryLock
鼓励对锁的不精确思考;它鼓励对可能为真或可能为假的不变量做出假设。这最终成为竞争的源头。
再思考一下,将 TryLock
构建到 Mutex
中与使用包装器相比有一个重要的好处:
失败的 TryLock
调用不会创建虚假的 happens-before 边缘来困惑竞争检测器。
还有:
基于通道的实现是可能的,但性能比较差。
我们之所以有 sync.Mutex
而不仅仅使用通道进行锁定,是有原因的。
英文:
> But the problem is, you cannot check if a mutex is locked. You can only block on it. Therefore it does not work
With possible Go 1.18 (Q1 2022), you will be able to test if a mutex is locked... without blocking on it.
See (as mentioned by Go 101) the issue 45435 from Tye McQueen :
> ## sync
: add Mutex.TryLock
This is followed by CL 319769, with the caveat:
> Use of these functions is almost (but not) always a bad idea.
>
> Very rarely they are necessary, and third-party implementations (using a mutex and an atomic word, say) cannot integrate as well with the race detector as implementations in package sync itself.
The objections (since retracted) were:
> Locks are for protecting invariants.
If the lock is held by someone else, there is nothing you can say about the invariant.
>
> TryLock
encourages imprecise thinking about locks; it encourages making assumptions about the invariants that may or may not be true.
That ends up being its own source of races.
>
> Thinking more about this, there is one important benefit to building TryLock
into Mutex
, compared to a wrapper:
failed TryLock
calls wouldn't create spurious happens-before edges to confuse the race detector.
And:
> A channel-based implementation is possible, but performs poorly in comparison.
There's a reason we have sync.Mutex
rather than just using channel for locking.
答案6
得分: 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
}
参见:https://play.golang.org/p/vUUsHcT3L-
英文:
I came up with the following generic solution for that:
Works for me, or do you see any problem with that?
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
}
答案7
得分: 0
这个包怎么样:https://github.com/viney-shih/go-lock。它使用通道和信号量(golang.org/x/sync/semaphore)来解决你的问题。
go-lock
除了提供Lock和Unlock函数外,还实现了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()) // 返回 false
// TryLockWithTimeout without blocking
fmt.Println("Return", casMut.TryLockWithTimeout(50*time.Millisecond)) // 返回 false
// TryLockWithContext without blocking
ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
defer cancel()
fmt.Println("Return", casMut.TryLockWithContext(ctx)) // 返回 false
// 输出:
// Return false
// Return false
// Return false
}
英文:
How about this package: https://github.com/viney-shih/go-lock . It use channel and semaphore (golang.org/x/sync/semaphore) to solve your problem.
go-lock
implements TryLock
, TryLockWithTimeout
and TryLockWithContext
functions in addition to Lock and Unlock. It provides flexibility to control the resources.
Examples:
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
}
答案8
得分: 0
让我们保持简单:
package main
import (
"fmt"
"time"
"golang.org/x/sync/semaphore"
)
var sem *semaphore.NewWeighted(1)
func init() {
sem = semaphore.NewWeighted(1)
}
func doSomething() {
if !sem.TryAcquire(1) {
return errors.New("我很忙")
}
defer sem.Release(1)
fmt.Println("我正在做我的工作,然后我会小睡一会儿")
time.Sleep(10)
}
func main() {
go func() {
doSomething()
}()
}
英文:
Lets keep it simple:
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()
}()
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论