Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Golang - lock per value

Tags:

go

I am writing a golang api that accepts a tableName value and a updEpoch value, ie:

curl -F "tableName=abc" -F "updEpoch=123" myhost:8080/singleroute
curl -F "tableName=abc" -F "updEpoch=456" myhost:8080/singleroute
curl -F "tableName=def" -F "updEpoch=123" myhost:8080/singleroute
curl -F "tableName=def" -F "updEpoch=345" myhost:8080/singleroute

I want to allow multiple different tableName requests to be handled in parallel BUT only 1 request per tableName at the same time. So in above example, if above 4 requests fired at same time, then 1st and 3rd should be able to run at same time (as unique tableNames), but 2nd will only start once 1st finishes and 4th will only start once 3rd finishes. When I was researching mutex no example seemed to fit this case, I don't want to hardcode abc/def.etc anywhere in the code as same rule should apply to any arbitrary tableName.

my guess based on Crowman's help:

package main

import (
    "fmt"
    "sync"
    "time"
    "http"
)
km := KeyedMutex{}
type KeyedMutex struct {
    mutexes sync.Map // Zero value is empty and ready for use
}

func (m *KeyedMutex) Lock(key string) func() {
    value, _ := m.mutexes.LoadOrStore(key, &sync.Mutex{})
    mtx := value.(*sync.Mutex)
    mtx.Lock()

    return func() { mtx.Unlock() }
}

func myFunc(key string, data string) string {
  //do some stuff
  return "done for key:"+key+", data: "+data
}

func main() {
    key := //some form value sent to my api 
    data := //some form value sent to my api 
    unlock := km.Lock(key)
    defer unlock()
    retVal := myFunc(key, data)
}
like image 265
tooptoop4 Avatar asked Oct 28 '20 00:10

tooptoop4


Video Answer


2 Answers

You can use a sync.Map with your table name as a key and a *sync.Mutex as the value.

For example:

package main

import (
    "fmt"
    "sync"
    "time"
)

type KeyedMutex struct {
    mutexes sync.Map // Zero value is empty and ready for use
}

func (m *KeyedMutex) Lock(key string) func() {
    value, _ := m.mutexes.LoadOrStore(key, &sync.Mutex{})
    mtx := value.(*sync.Mutex)
    mtx.Lock()

    return func() { mtx.Unlock() }
}

func main() {
    wg := sync.WaitGroup{}
    km := KeyedMutex{}

    for _, job := range []struct {
        key  string
        data string
    }{
        {key: "abc", data: "123"},
        {key: "abc", data: "456"},
        {key: "def", data: "123"},
        {key: "def", data: "456"},
    } {
        var job = job
        wg.Add(1)

        go func() {
            defer wg.Done()

            unlock := km.Lock(job.key)
            defer unlock()

            fmt.Printf("%s:%s mutex acquired\n", job.key, job.data)
            time.Sleep(time.Second * 1) // To ensure some goroutines visibly block
            fmt.Printf("%s:%s done\n", job.key, job.data)
        }()
    }

    wg.Wait()
}

with sample output:

crow@mac:$ ./mut
def:456 mutex acquired
abc:456 mutex acquired
abc:456 done
def:456 done
abc:123 mutex acquired
def:123 mutex acquired
def:123 done
abc:123 done
crow@mac:$

to show that requests with distinct table names acquire a mutex immediately, but requests with the same table name are serialized.

like image 124
Crowman Avatar answered Oct 17 '22 17:10

Crowman


You can create a package level var map[string]*sync.Mutex and lock corresponding mutex that you will get/create by table name.

like image 35
Alexander Trakhimenok Avatar answered Oct 03 '22 03:10

Alexander Trakhimenok