Good way to return on locked mutex in go

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

}

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


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
}

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
}

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")
    }
}

Tags:

Go