rowlock: go.yhsif.com/rowlock Index | Examples | Files

package rowlock

import "go.yhsif.com/rowlock"

Package rowlock provides an implementation of row lock.

A row lock is a set of locks associated with rows. Instead of locking and unlocking globally, you only operate locks on a row level.

RowLock provides optional RLock and RUnlock functions to use separated read and write locks. In order to take advantage of them, NewLocker function used in NewRowLock must returns an implementation of RWLocker (for example, RWMutexNewLocker returns a new sync.RWMutex). If the locker returned by NewLocker didn't implement RLocker function defined in RWLocker, RLock will work the same as Lock and RUnlock will work the same as Unlock.

Code:

lock := rowlock.NewRowLock(rowlock.MutexNewLocker)
key1 := "key1"
key2 := "key2"
round := time.Millisecond * 50

keys := []string{key1, key1, key2, key2}
sleeps := []time.Duration{
    time.Millisecond * 250,
    time.Millisecond * 200,
    time.Millisecond * 350,
    time.Millisecond * 300,
}

var wg sync.WaitGroup
wg.Add(len(keys))

for i := range keys {
    go func(key string, sleep time.Duration) {
        started := time.Now()
        defer wg.Done()
        time.Sleep(sleep)
        lock.Lock(key)
        defer lock.Unlock(key)
        elapsed := time.Now().Sub(started).Round(round)
        // The same key with longer sleep will get an elapsed time about
        // 2 * the same key with shorter sleep instead of its own sleep time,
        // because that's when the other goroutine releases the lock.
        fmt.Printf("%s got lock after about %v\n", key, elapsed)
        time.Sleep(sleep)
    }(keys[i], sleeps[i])
}

wg.Wait()

Output:

key1 got lock after about 200ms
key2 got lock after about 300ms
key1 got lock after about 400ms
key2 got lock after about 600ms

Index

Examples

Package Files

doc.go rowlock.go

func MutexNewLocker Uses

func MutexNewLocker() sync.Locker

MutexNewLocker is a NewLocker using sync.Mutex.

func RWMutexNewLocker Uses

func RWMutexNewLocker() sync.Locker

RWMutexNewLocker is a NewLocker using sync.RWMutex.

Code:

lock := rowlock.NewRowLock(rowlock.RWMutexNewLocker)
key1 := "key1"
key2 := "key2"
round := time.Millisecond * 50

var wg sync.WaitGroup

readKeys := []string{key1, key1, key2, key2}
readSleeps := []time.Duration{
    time.Millisecond * 250,
    time.Millisecond * 200,
    time.Millisecond * 350,
    time.Millisecond * 300,
}
wg.Add(len(readKeys))

writeKeys := []string{key1, key1, key2, key2}
writeSleeps := []time.Duration{
    time.Millisecond * 350,
    time.Millisecond * 150,
    time.Millisecond * 450,
    time.Millisecond * 200,
}
wg.Add(len(writeKeys))

// Read locks
for i := range readKeys {
    go func(key string, sleep time.Duration) {
        started := time.Now()
        defer wg.Done()
        time.Sleep(sleep)
        lock.RLock(key)
        defer lock.RUnlock(key)
        elapsed := time.Now().Sub(started).Round(round)
        // Should be:
        //   max(shorter write sleep time * 2, self sleep time)
        fmt.Printf("%s got read lock after about %v\n", key, elapsed)
        time.Sleep(sleep)
    }(readKeys[i], readSleeps[i])
}

// Write locks
for i := range writeKeys {
    go func(key string, sleep time.Duration) {
        started := time.Now()
        defer wg.Done()
        time.Sleep(sleep)
        lock.Lock(key)
        defer lock.Unlock(key)
        elapsed := time.Now().Sub(started).Round(round)
        // For the longer sleep one, it should be
        //   max(shorter write * 2, longer read) + longer read
        // instead of it's self sleep time
        fmt.Printf("%s got lock after about %v\n", key, elapsed)
        time.Sleep(sleep)
    }(writeKeys[i], writeSleeps[i])
}

wg.Wait()

Output:

key1 got lock after about 150ms
key2 got lock after about 200ms
key1 got read lock after about 300ms
key1 got read lock after about 300ms
key2 got read lock after about 400ms
key2 got read lock after about 400ms
key1 got lock after about 550ms
key2 got lock after about 750ms

type NewLocker Uses

type NewLocker func() sync.Locker

NewLocker defines a type of function that can be used to create a new Locker.

type RWLocker Uses

type RWLocker interface {
    sync.Locker

    RLocker() sync.Locker
}

RWLocker is the abstracted interface of sync.RWMutex.

type Row Uses

type Row = defaultdict.Comparable

Row is the type of a row.

It must be comparable: https://golang.org/ref/spec#Comparison_operators.

type RowLock Uses

type RowLock struct {
    // contains filtered or unexported fields
}

RowLock defines a set of locks.

When you do Lock/Unlock operations, you don't do them on a global scale. Instead, a Lock/Unlock operation is operated on a given row.

If NewLocker returns an implementation of RWLocker in NewRowLock, the RowLock can be locked separately for read in RLock and RUnlock functions. Otherwise, RLock is the same as Lock and RUnlock is the same as Unlock.

func NewRowLock Uses

func NewRowLock(f NewLocker) *RowLock

NewRowLock creates a new RowLock with the given NewLocker.

func (*RowLock) Lock Uses

func (rl *RowLock) Lock(row Row)

Lock locks a row.

If this is a new row, a new locker will be created using the NewLocker specified in NewRowLock.

func (*RowLock) RLock Uses

func (rl *RowLock) RLock(row Row)

RLock locks a row for read.

It only works as expected when NewLocker specified in NewRowLock returns an implementation of RWLocker. Otherwise, it's the same as Lock.

func (*RowLock) RUnlock Uses

func (rl *RowLock) RUnlock(row Row)

RUnlock unlocks a row for read.

It only works as expected when NewLocker specified in NewRowLock returns an implementation of RWLocker. Otherwise, it's the same as Unlock.

func (*RowLock) Unlock Uses

func (rl *RowLock) Unlock(row Row)

Unlock unlocks a row.

Package rowlock imports 2 packages (graph). Updated 2021-01-08. Refresh now. Tools for package owners.