Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Good way to return on locked mutex in go

Tags:

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?

like image 884
Subby Avatar asked Jul 20 '17 07:07

Subby


People also ask

What happens if you try to lock a locked mutex?

Mutexes are used to protect shared resources. If the mutex is already locked by another thread, the thread waits for the mutex to become available. The thread that has locked a mutex becomes its current owner and remains the owner until the same thread has unlocked it.

How does mutex lock work Golang?

Any code present between a call to Lock and Unlock will be executed by only one Goroutine. If one Goroutine already has the lock and if a new Goroutine is trying to get the lock, then the new Goroutine will be stopped until the mutex is unlocked.

Can I unlock a mutex from another thread?

A normal mutex cannot be locked repeatedly by the owner. Attempts by a thread to relock an already held mutex, or to lock a mutex that was held by another thread when that thread terminated, cause a deadlock condition. A recursive mutex can be locked repeatedly by the owner.


1 Answers

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 } 
like image 77
sberry Avatar answered Sep 20 '22 00:09

sberry