vqx46
vqx46

Reputation: 47

function with mutex.Lock that returns before unlocking

I need to use mutex to read a variable and if the variable is 0, return from the function. This would prevent the mutex from Unlocking though.

I know that I could simply put a mutex.Unlock just before the return but it does not seem nice / correct.

I can't even do a defer mutex.Unlock() at the beginning of the function because the the code after requires a lot of time to run.

Is there a correct way to do so?

This is the example:

func mutexfunc() {
    mutex.Lock()
    
    if variable == 0 {
        return
    }
    
    mutex.Unlock()

    // long execution time (mutex must be unlocked)
}

UPDATE:

this is the solution I prefer:

var mutex = &sync.Mutex{}

var mutexSensibleVar = 0

func main() {
    if withLock(func() bool { return mutexSensibleVar == 1 }) {
        fmt.Println("it's true")
    } else {
        fmt.Println("it's false")
    }

    fmt.Println("end")
}

func withLock(f func() bool) bool {
    mutex.Lock()
    defer mutex.Unlock()

    return f()
}

Upvotes: 1

Views: 1175

Answers (3)

torek
torek

Reputation: 487805

Also consider the (clumsy but readable) variant with a "need to unlock" boolean:

func f(arg1 argtype1, arg2 argtype2) ret returntype {
    var needToUnlock bool
    defer func() {
        if needToUnlock {
            lock.Unlock()
        }
    }()
    // arbitrary amount of code here that runs unlocked
    lock.Lock()
    needToUnlock = true
    // arbitrary amount of code here that runs locked
    lock.Unlock()
    needToUnlock = false
    // arbitrary amount of code here that runs unlocked
    // repeat as desired
}

You can wrap such a thing up in a type:

type DeferableLock struct {
    L        Locker
    isLocked bool
}

func (d *DeferableLock) Lock() {
    d.L.Lock()
    d.isLocked = true
}

func (d *DeferableLock) Unlock() {
    d.L.Unlock()
    d.isLocked = false
}

func (d *DeferableLock) EnsureUnlocked() {
    if d.isLocked {
        d.Unlock()
    }
}

func NewDeferableLock(l Locker) *DeferableLock() {
    return &DeferableLock{L: l}
}

You can now wrap any sync.Locker with a DeferableLock. At functions like f, use the deferable wrapper to wrap the lock, and call defer d.EnsureUnlock.

(Any resemblance to sync.Cond is entirely deliberate.)

Upvotes: 0

Paul Hankin
Paul Hankin

Reputation: 58201

You can separate the locked part into its own function.

func varIsZero() bool {
    mutex.Lock()
    defer mutex.Unlock()
    return variable == 0
}

func mutexfunc() {
    if varIsZero() { return }
    ...
}

An alternative would be to use an anonymous function inside mutexfunc rather than a completely independent function, but it's a matter of taste here.

Upvotes: 3

Burak Serdar
Burak Serdar

Reputation: 51502

If you can't use defer, which is something you can't do here, you have to do the obvious:

func mutexfunc() {
    mutex.Lock()
    
    if variable == 0 {
        mutex.Unlock()
        return
    }
    
    mutex.Unlock()

    // long execution time (mutex must be unlocked)
}

If the mutex is there only to protect that variable (that is, there isn't other code you're not showing us), you can also use sync/atomic:

func f() {
   if atomic.LoadInt64(&variable) ==0 {
      return
   }
   ...
}

Upvotes: 3

Related Questions