Good way to return on locked mutex in go

huangapple go评论114阅读模式
英文:

Good way to return on locked mutex in go

问题

以下是翻译的内容:

以下问题:
我有一个函数,只允许一个调用者执行。
如果有人尝试调用该函数,而且它已经在忙碌中,第二个调用者应立即返回错误。

我尝试了以下方法:

1. 使用互斥锁

这个方法很简单。但问题是,你无法检查互斥锁是否被锁定。你只能在它上面阻塞。因此这种方法行不通。

2. 在通道上等待

  1. var canExec = make(chan bool, 1)
  2. func init() {
  3. canExec <- true
  4. }
  5. func onlyOne() error {
  6. select {
  7. case <-canExec:
  8. default:
  9. return errors.New("already busy")
  10. }
  11. defer func() {
  12. fmt.Println("done")
  13. canExec <- true
  14. }()
  15. // 做一些事情
  16. }

我不喜欢这个方法的原因:

  • 看起来很混乱
  • 很容易错误地在通道上阻塞/错误地向通道写入数据

3. 互斥锁和共享状态的混合

  1. var open = true
  2. var myMutex *sync.Mutex
  3. func canExec() bool {
  4. myMutex.Lock()
  5. defer myMutex.Unlock()
  6. if open {
  7. open = false
  8. return true
  9. }
  10. return false
  11. }
  12. func endExec() {
  13. myMutex.Lock()
  14. defer myMutex.Unlock()
  15. open = true
  16. }
  17. func onlyOne() error {
  18. if !canExec() {
  19. return errors.New("busy")
  20. }
  21. defer endExec()
  22. // 做一些事情
  23. return nil
  24. }

我也不喜欢这个方法。使用带有互斥锁的共享变量不太好。

还有其他的想法吗?

英文:

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

  1. var canExec = make(chan bool, 1)
  2. func init() {
  3. canExec &lt;- true
  4. }
  5. func onlyOne() error {
  6. select {
  7. case &lt;-canExec:
  8. default:
  9. return errors.New(&quot;already busy&quot;)
  10. }
  11. defer func() {
  12. fmt.Println(&quot;done&quot;)
  13. canExec &lt;- true
  14. }()
  15. // do stuff
  16. }

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

  1. var open = true
  2. var myMutex *sync.Mutex
  3. func canExec() bool {
  4. myMutex.Lock()
  5. defer myMutex.Unlock()
  6. if open {
  7. open = false
  8. return true
  9. }
  10. return false
  11. }
  12. func endExec() {
  13. myMutex.Lock()
  14. defer myMutex.Unlock()
  15. open = true
  16. }
  17. func onlyOne() error {
  18. if !canExec() {
  19. return errors.New(&quot;busy&quot;)
  20. }
  21. defer endExec()
  22. // do stuff
  23. return nil
  24. }

I don't like this either. Using a shard variable with mutex is not that nice.

Any other idea?

答案1

得分: 36

我会把你提供的内容翻译成中文,以下是翻译的结果:

我会提出我的偏好 - 使用atomic包

  1. var (
  2. locker uint32
  3. errLocked = errors.New("被锁住了伙计")
  4. )
  5. func OneAtATime(d time.Duration) error {
  6. if !atomic.CompareAndSwapUint32(&locker, 0, 1) { // <-----------------------------
  7. return errLocked // 所有逻辑都在这四行里面 |
  8. } // 这四行代码 |
  9. defer atomic.StoreUint32(&locker, 0) // <-----------------------------
  10. // 在这里进行逻辑处理,但是我们会休眠
  11. time.Sleep(d)
  12. return nil
  13. }

这个想法非常简单。将初始值设置为0(uint32的零值)。函数中的第一件事是检查locker的值是否为0,如果是,则将其更改为1。所有这些操作都是原子性的。如果失败,简单地返回一个错误(或者以其他方式处理被锁定的状态)。如果成功,立即延迟将值(现在为1)替换为0。当然你不一定非要使用defer,但是如果在返回之前不将值设置回0,那么函数将无法再次运行。

在完成这4行设置之后,你可以按照正常的方式进行其他操作。

你可以通过为状态使用命名值来使代码更加清晰。

  1. const (
  2. stateUnlocked uint32 = iota
  3. stateLocked
  4. )
  5. var (
  6. locker = stateUnlocked
  7. errLocked = errors.New("被锁住了伙计")
  8. )
  9. func OneAtATime(d time.Duration) error {
  10. if !atomic.CompareAndSwapUint32(&locker, stateUnlocked, stateLocked) {
  11. return errLocked
  12. }
  13. defer atomic.StoreUint32(&locker, stateUnlocked)
  14. // 在这里进行逻辑处理,但是我们会休眠
  15. time.Sleep(d)
  16. return nil
  17. }
英文:

I'll throw my preference out there - use the atomic package.

  1. var (
  2. locker uint32
  3. errLocked = errors.New(&quot;Locked out buddy&quot;)
  4. )
  5. func OneAtATime(d time.Duration) error {
  6. if !atomic.CompareAndSwapUint32(&amp;locker, 0, 1) { // &lt;-----------------------------
  7. return errLocked // All logic in these |
  8. } // four lines |
  9. defer atomic.StoreUint32(&amp;locker, 0) // &lt;-----------------------------
  10. // logic here, but we will sleep
  11. time.Sleep(d)
  12. return nil
  13. }

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.

  1. const (
  2. stateUnlocked uint32 = iota
  3. stateLocked
  4. )
  5. var (
  6. locker = stateUnlocked
  7. errLocked = errors.New(&quot;Locked out buddy&quot;)
  8. )
  9. func OneAtATime(d time.Duration) error {
  10. if !atomic.CompareAndSwapUint32(&amp;locker, stateUnlocked, stateLocked) {
  11. return errLocked
  12. }
  13. defer atomic.StoreUint32(&amp;locker, stateUnlocked)
  14. // logic here, but we will sleep
  15. time.Sleep(d)
  16. return nil
  17. }

答案2

得分: 31

你可以使用信号量来实现这个(go get golang.org/x/sync/semaphore

  1. package main
  2. import (
  3. "errors"
  4. "fmt"
  5. "sync"
  6. "time"
  7. "golang.org/x/sync/semaphore"
  8. )
  9. var sem = semaphore.NewWeighted(1)
  10. func main() {
  11. var wg sync.WaitGroup
  12. for i := 0; i < 10; i++ {
  13. wg.Add(1)
  14. go func() {
  15. defer wg.Done()
  16. if err := onlyOne(); err != nil {
  17. fmt.Println(err)
  18. }
  19. }()
  20. time.Sleep(time.Second)
  21. }
  22. wg.Wait()
  23. }
  24. func onlyOne() error {
  25. if !sem.TryAcquire(1) {
  26. return errors.New("busy")
  27. }
  28. defer sem.Release(1)
  29. fmt.Println("working")
  30. time.Sleep(5 * time.Second)
  31. return nil
  32. }

希望对你有帮助!

英文:

You can use a semaphore for this (go get golang.org/x/sync/semaphore)

  1. package main
  2. import (
  3. &quot;errors&quot;
  4. &quot;fmt&quot;
  5. &quot;sync&quot;
  6. &quot;time&quot;
  7. &quot;golang.org/x/sync/semaphore&quot;
  8. )
  9. var sem = semaphore.NewWeighted(1)
  10. func main() {
  11. var wg sync.WaitGroup
  12. for i := 0; i &lt; 10; i++ {
  13. wg.Add(1)
  14. go func() {
  15. defer wg.Done()
  16. if err := onlyOne(); err != nil {
  17. fmt.Println(err)
  18. }
  19. }()
  20. time.Sleep(time.Second)
  21. }
  22. wg.Wait()
  23. }
  24. func onlyOne() error {
  25. if !sem.TryAcquire(1) {
  26. return errors.New(&quot;busy&quot;)
  27. }
  28. defer sem.Release(1)
  29. fmt.Println(&quot;working&quot;)
  30. time.Sleep(5 * time.Second)
  31. return nil
  32. }

答案3

得分: 4

你可以使用带有select语句的标准通道方法。

  1. var (
  2. ch = make(chan bool)
  3. )
  4. func main() {
  5. i := 0
  6. wg := sync.WaitGroup{}
  7. for i < 100 {
  8. i++
  9. wg.Add(1)
  10. go func() {
  11. defer wg.Done()
  12. err := onlyOne()
  13. if err != nil {
  14. fmt.Println("错误:", err)
  15. } else {
  16. fmt.Println("好的")
  17. }
  18. }()
  19. go func() {
  20. ch <- true
  21. }()
  22. }
  23. wg.Wait()
  24. }
  25. func onlyOne() error {
  26. select {
  27. case <-ch:
  28. // 做一些事情
  29. return nil
  30. default:
  31. return errors.New("忙碌")
  32. }
  33. }

请注意,我只翻译了代码部分,其他内容被忽略了。

英文:

You could use standard channel approach with select statement.

  1. var (
  2. ch = make(chan bool)
  3. )
  4. func main() {
  5. i := 0
  6. wg := sync.WaitGroup{}
  7. for i &lt; 100 {
  8. i++
  9. wg.Add(1)
  10. go func() {
  11. defer wg.Done()
  12. err := onlyOne()
  13. if err != nil {
  14. fmt.Println(&quot;Error: &quot;, err)
  15. } else {
  16. fmt.Println(&quot;Ok&quot;)
  17. }
  18. }()
  19. go func() {
  20. ch &lt;- true
  21. }()
  22. }
  23. wg.Wait()
  24. }
  25. func onlyOne() error {
  26. select {
  27. case &lt;-ch:
  28. // do stuff
  29. return nil
  30. default:
  31. return errors.New(&quot;Busy&quot;)
  32. }
  33. }

答案4

得分: 4

你想要一个函数只执行一次,还是在给定的时间内执行一次?如果是前者,请参考https://golang.org/pkg/sync/#Once。

如果你想要一次执行一个解决方案:

  1. package main
  2. import (
  3. "fmt"
  4. "sync"
  5. "time"
  6. )
  7. // OnceAtATime 保护函数不被同时执行。
  8. // 示例:
  9. // func myFunc() { time.Sleep(10*time.Second) }
  10. // func main() {
  11. // once := OnceAtATime{}
  12. // once.Do(myFunc)
  13. // once.Do(myFunc) // 不会执行
  14. // }
  15. type OnceAtATime struct {
  16. m sync.Mutex
  17. executed bool
  18. }
  19. func (o *OnceAtATime) Do(f func()) {
  20. o.m.Lock()
  21. if o.executed {
  22. o.m.Unlock()
  23. return
  24. }
  25. o.executed = true
  26. o.m.Unlock()
  27. f()
  28. o.m.Lock()
  29. o.executed = false
  30. o.m.Unlock()
  31. }
  32. // Proof of concept
  33. func f(m int, done chan<- struct{}) {
  34. for i := 0; i < 10; i++ {
  35. fmt.Printf("%d: %d\n", m, i)
  36. time.Sleep(250 * time.Millisecond)
  37. }
  38. close(done)
  39. }
  40. func main() {
  41. done := make(chan struct{})
  42. once := OnceAtATime{}
  43. go once.Do(func() { f(1, done) })
  44. go once.Do(func() { f(2, done) })
  45. <-done
  46. done = make(chan struct{})
  47. go once.Do(func() { f(3, done) })
  48. <-done
  49. }

链接: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:

  1. package main
  2. import (
  3. &quot;fmt&quot;
  4. &quot;sync&quot;
  5. &quot;time&quot;
  6. )
  7. // OnceAtATime protects function from being executed simultaneously.
  8. // Example:
  9. // func myFunc() { time.Sleep(10*time.Second) }
  10. // func main() {
  11. // once := OnceAtATime{}
  12. // once.Do(myFunc)
  13. // once.Do(myFunc) // not executed
  14. // }
  15. type OnceAtATime struct {
  16. m sync.Mutex
  17. executed bool
  18. }
  19. func (o *OnceAtATime) Do(f func()) {
  20. o.m.Lock()
  21. if o.executed {
  22. o.m.Unlock()
  23. return
  24. }
  25. o.executed = true
  26. o.m.Unlock()
  27. f()
  28. o.m.Lock()
  29. o.executed = false
  30. o.m.Unlock()
  31. }
  32. // Proof of concept
  33. func f(m int, done chan&lt;- struct{}) {
  34. for i := 0; i &lt; 10; i++ {
  35. fmt.Printf(&quot;%d: %d\n&quot;, m, i)
  36. time.Sleep(250 * time.Millisecond)
  37. }
  38. close(done)
  39. }
  40. func main() {
  41. done := make(chan struct{})
  42. once := OnceAtATime{}
  43. go once.Do(func() { f(1, done) })
  44. go once.Do(func() { f(2, done) })
  45. &lt;-done
  46. done = make(chan struct{})
  47. go once.Do(func() { f(3, done) })
  48. &lt;-done
  49. }

https://play.golang.org/p/nZcEcWAgKp

答案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

我为此提出了以下通用解决方案:

对我来说可以工作,你看到有什么问题吗?

  1. import (
  2. "sync"
  3. )
  4. const (
  5. ONLYONECALLER_LOCK = "onlyonecaller"
  6. ANOTHER_LOCK = "onlyonecaller"
  7. )
  8. var locks = map[string]bool{}
  9. var mutex = &sync.Mutex{}
  10. func Lock(lock string) bool {
  11. mutex.Lock()
  12. defer mutex.Unlock()
  13. locked, ok := locks[lock]
  14. if !ok {
  15. locks[lock] = true
  16. return true
  17. }
  18. if locked {
  19. return false
  20. }
  21. locks[lock] = true
  22. return true
  23. }
  24. func IsLocked(lock string) bool {
  25. mutex.Lock()
  26. defer mutex.Unlock()
  27. locked, ok := locks[lock]
  28. if !ok {
  29. return false
  30. }
  31. return locked
  32. }
  33. func Unlock(lock string) {
  34. mutex.Lock()
  35. defer mutex.Unlock()
  36. locked, ok := locks[lock]
  37. if !ok {
  38. return
  39. }
  40. if !locked {
  41. return
  42. }
  43. locks[lock] = false
  44. }

参见: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?

  1. import (
  2. &quot;sync&quot;
  3. )
  4. const (
  5. ONLYONECALLER_LOCK = &quot;onlyonecaller&quot;
  6. ANOTHER_LOCK = &quot;onlyonecaller&quot;
  7. )
  8. var locks = map[string]bool{}
  9. var mutex = &amp;sync.Mutex{}
  10. func Lock(lock string) bool {
  11. mutex.Lock()
  12. defer mutex.Unlock()
  13. locked, ok := locks[lock]
  14. if !ok {
  15. locks[lock] = true
  16. return true
  17. }
  18. if locked {
  19. return false
  20. }
  21. locks[lock] = true
  22. return true
  23. }
  24. func IsLocked(lock string) bool {
  25. mutex.Lock()
  26. defer mutex.Unlock()
  27. locked, ok := locks[lock]
  28. if !ok {
  29. return false
  30. }
  31. return locked
  32. }
  33. func Unlock(lock string) {
  34. mutex.Lock()
  35. defer mutex.Unlock()
  36. locked, ok := locks[lock]
  37. if !ok {
  38. return
  39. }
  40. if !locked {
  41. return
  42. }
  43. locks[lock] = false
  44. }

see: https://play.golang.org/p/vUUsHcT3L-

答案7

得分: 0

这个包怎么样:https://github.com/viney-shih/go-lock。它使用通道信号量golang.org/x/sync/semaphore)来解决你的问题。

go-lock除了提供Lock和Unlock函数外,还实现了TryLock、TryLockWithTimeout和TryLockWithContext函数。它提供了灵活性来控制资源。

示例代码:

  1. package main
  2. import (
  3. "fmt"
  4. "time"
  5. "context"
  6. lock "github.com/viney-shih/go-lock"
  7. )
  8. func main() {
  9. casMut := lock.NewCASMutex()
  10. casMut.Lock()
  11. defer casMut.Unlock()
  12. // TryLock without blocking
  13. fmt.Println("Return", casMut.TryLock()) // 返回 false
  14. // TryLockWithTimeout without blocking
  15. fmt.Println("Return", casMut.TryLockWithTimeout(50*time.Millisecond)) // 返回 false
  16. // TryLockWithContext without blocking
  17. ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
  18. defer cancel()
  19. fmt.Println("Return", casMut.TryLockWithContext(ctx)) // 返回 false
  20. // 输出:
  21. // Return false
  22. // Return false
  23. // Return false
  24. }
英文:

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:

  1. package main
  2. import (
  3. &quot;fmt&quot;
  4. &quot;time&quot;
  5. &quot;context&quot;
  6. lock &quot;github.com/viney-shih/go-lock&quot;
  7. )
  8. func main() {
  9. casMut := lock.NewCASMutex()
  10. casMut.Lock()
  11. defer casMut.Unlock()
  12. // TryLock without blocking
  13. fmt.Println(&quot;Return&quot;, casMut.TryLock()) // Return false
  14. // TryLockWithTimeout without blocking
  15. fmt.Println(&quot;Return&quot;, casMut.TryLockWithTimeout(50*time.Millisecond)) // Return false
  16. // TryLockWithContext without blocking
  17. ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
  18. defer cancel()
  19. fmt.Println(&quot;Return&quot;, casMut.TryLockWithContext(ctx)) // Return false
  20. // Output:
  21. // Return false
  22. // Return false
  23. // Return false
  24. }

答案8

得分: 0

让我们保持简单:

  1. package main
  2. import (
  3. "fmt"
  4. "time"
  5. "golang.org/x/sync/semaphore"
  6. )
  7. var sem *semaphore.NewWeighted(1)
  8. func init() {
  9. sem = semaphore.NewWeighted(1)
  10. }
  11. func doSomething() {
  12. if !sem.TryAcquire(1) {
  13. return errors.New("我很忙")
  14. }
  15. defer sem.Release(1)
  16. fmt.Println("我正在做我的工作,然后我会小睡一会儿")
  17. time.Sleep(10)
  18. }
  19. func main() {
  20. go func() {
  21. doSomething()
  22. }()
  23. }
英文:

Lets keep it simple:

  1. package main
  2. import (
  3. &quot;fmt&quot;
  4. &quot;time&quot;
  5. &quot;golang.org/x/sync/semaphore&quot;
  6. )
  7. var sem *semaphore.NewWeighted(1)
  8. func init() {
  9. sem = emaphore.NewWeighted(1)
  10. }
  11. func doSomething() {
  12. if !sem.TryAcquire(1) {
  13. return errors.New(&quot;I&#39;m busy&quot;)
  14. }
  15. defer sem.Release(1)
  16. fmt.Println(&quot;I&#39;m doing my work right now, then I&#39;ll take a nap&quot;)
  17. time.Sleep(10)
  18. }
  19. func main() {
  20. go func() {
  21. doSomething()
  22. }()
  23. }

huangapple
  • 本文由 发表于 2017年7月20日 15:46:56
  • 转载请务必保留本文链接:https://go.coder-hub.com/45208536.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定