在go中锁定互斥锁的好方法

Sub*_*bby 3 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
    }()

    // 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)


dev*_*max 6

您可以为此使用信号量(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)