gmlock-memory lock module of gf framework

Keywords: Go

Source of the article: http://gf.johng.cn/os/gmlock/...

Memory lock. The module contains two object features:

  1. Locker memory lock, which supports generating memory locks according to given key names, and supports Try*Lock and lock expiration characteristics.
  2. Mutex encapsulates sync.Mutex at the bottom of the standard library, adding Try*Lock features.

How to use it:

import "gitee.com/johng/gf/g/os/gmlock"

Use scenarios:

  1. Any scenario that requires concurrent security can replace sync.Mutex.
  2. The scenario of Try*Lock needs to be used (no blocking is required to wait for the lock to be released);
  3. Need to create mutexes dynamically, or need to maintain a large number of dynamic locks scenarios;

Method list

func Lock(key string, expire ...int)
func RLock(key string, expire ...int)
func RUnlock(key string)
func TryLock(key string, expire ...int) bool
func TryRLock(key string, expire ...int) bool
func Unlock(key string)
type Locker
    func New() *Locker
    func (l *Locker) Lock(key string, expire ...int)
    func (l *Locker) RLock(key string, expire ...int)
    func (l *Locker) RUnlock(key string)
    func (l *Locker) TryLock(key string, expire ...int) bool
    func (l *Locker) TryRLock(key string, expire ...int) bool
    func (l *Locker) Unlock(key string)
type Mutex
    func NewMutex() *Mutex
    func (l *Mutex) Lock()
    func (l *Mutex) RLock()
    func (l *Mutex) RUnlock()
    func (l *Mutex) TryLock() bool
    func (l *Mutex) TryRLock() bool
    func (l *Mutex) Unlock()

Example 1, Basic usage

package main

import (
    "time"
    "sync"
    "gitee.com/johng/gf/g/os/glog"
    "gitee.com/johng/gf/g/os/gmlock"
)

func main() {
    key := "lock"
    wg  := sync.WaitGroup{}
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func(i int) {
            gmlock.Lock(key)
            glog.Println(i)
            time.Sleep(time.Second)
            gmlock.Unlock(key)
            wg.Done()
        }(i)
    }
    wg.Wait()
}

In this example, we simulate opening 10 goroutines at the same time, but only one goroutine can get the lock at the same time. The goroutine that obtains the lock quits one second after execution, and the other goroutines can get the lock.

After execution, the output is as follows:

2018-10-15 23:57:28.295 9
2018-10-15 23:57:29.296 0
2018-10-15 23:57:30.296 1
2018-10-15 23:57:31.296 2
2018-10-15 23:57:32.296 3
2018-10-15 23:57:33.297 4
2018-10-15 23:57:34.297 5
2018-10-15 23:57:35.297 6
2018-10-15 23:57:36.298 7
2018-10-15 23:57:37.298 8

Example 2, expiration control

We use expiration time control to implement the above examples.

package main

import (
    "sync"
    "gitee.com/johng/gf/g/os/glog"
    "gitee.com/johng/gf/g/os/gmlock"
)

func main() {
    key := "lock"
    wg  := sync.WaitGroup{}
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func(i int) {
            gmlock.Lock(key, 1000)
            glog.Println(i)
            wg.Done()
        }(i)
    }
    wg.Wait()
}

After execution, the output is as follows:

2018-10-15 23:59:14.663 9
2018-10-15 23:59:15.663 4
2018-10-15 23:59:16.663 0
2018-10-15 23:59:17.664 1
2018-10-15 23:59:18.664 2
2018-10-15 23:59:19.664 3
2018-10-15 23:59:20.664 6
2018-10-15 23:59:21.664 5
2018-10-15 23:59:22.665 7
2018-10-15 23:59:23.665 8

Example 3, TryLock non-blocking lock

The TryLock method has a return value, which indicates that it is used to attempt to acquire a lock, true if it succeeds, and false if the acquisition fails (that is, the lock has been acquired by other goroutine s).

package main

import (
    "sync"
    "gitee.com/johng/gf/g/os/glog"
    "time"
    "gitee.com/johng/gf/g/os/gmlock"
)

func main() {
    key := "lock"
    wg  := sync.WaitGroup{}
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func(i int) {
            if gmlock.TryLock(key) {
                glog.Println(i)
                time.Sleep(time.Second)
                gmlock.Unlock(key)
            } else {
                glog.Println(false)
            }
            wg.Done()
        }(i)
    }
    wg.Wait()
}

Similarly, in this example, only one goroutine can get a lock, and the other goroutines exit directly when TryLock fails.

After execution, the output is as follows:

2018-10-16 00:01:59.172 9
2018-10-16 00:01:59.172 false
2018-10-16 00:01:59.172 false
2018-10-16 00:01:59.172 false
2018-10-16 00:01:59.172 false
2018-10-16 00:01:59.172 false
2018-10-16 00:01:59.172 false
2018-10-16 00:01:59.172 false
2018-10-16 00:01:59.172 false
2018-10-16 00:01:59.176 false

Example 4, Multiple Lock Mechanism Conflicts

This example is used to demonstrate the handling of lock mechanism under complex logic.

package main

import (
    "gitee.com/johng/gf/g/os/gmlock"
    "time"
    "gitee.com/johng/gf/g/os/glog"
    "fmt"
)

// Memory Lock-Manual Unlock and Timing Unlock Conflict Check
func main() {
    key := "key"

    // First locking time
    gmlock.Lock(key, 1000)
    glog.Println("lock1")
    // At this time, the last time the timing unlock has expired.
    gmlock.Unlock(key)
    glog.Println("unlock1")

    fmt.Println()

    // Second lock, no time, and the timing unlock of the previous Lock takes effect during execution
    gmlock.Lock(key)
    glog.Println("lock2")
    go func() {
        // Normally it takes three seconds to execute this sentence.
        gmlock.Lock(key)
        glog.Println("lock by goroutine")
    }()
    time.Sleep(3*time.Second)
    // Then unlock again
    gmlock.Unlock(key)
    // Notice that it takes three seconds to execute this sentence.
    glog.Println("unlock2")

    // Blocking process
    select{}
}

After execution, the output is as follows:

2018-10-16 00:03:40.277 lock1
2018-10-16 00:03:40.279 unlock1

2018-10-16 00:03:40.279 lock2
2018-10-16 00:03:43.279 unlock2
2018-10-16 00:03:43.279 lock by goroutine

Example 5: Security control for concurrent writing of multiple files

There is a core method for writing log files in the glog module. Let's take a look at it. (The source code is located in the glog module.) /g/os/glog/glog_logger.go).

// The write lock here guarantees that only one line of logs will be written at the same time to prevent the case of string logs.
func (l *Logger) print(std io.Writer, s string) {
    // Prioritize using custom IO output
    if l.printHeader.Val() {
        s = l.format(s)
    }
    writer := l.GetWriter()
    if writer == nil {
        // If the set writer is empty, then the next step is to determine whether there is a file output setting.
        // Memory locks are used internally to ensure that concurrent writing of the same log file in glog does not cross-log (concurrent security)
        if f := l.getFilePointer(); f != nil {
            defer f.Close()
            key := l.path.Val()
            gmlock.Lock(key)
            _, err := io.WriteString(f, s)
            gmlock.Unlock(key)
            if err != nil {
                fmt.Fprintln(os.Stderr, err.Error())
            }
        }
    } else {
        l.doStdLockPrint(writer, s)
    }
    // Is output allowed to standard output?
    if l.alsoStdPrint.Val() {
        l.doStdLockPrint(std, s)
    }
}

Among them:

gmlock.Lock(key)
...
gmlock.Unlock(key)

The key variable represents the absolute path of the log file. When multiple goroutine s write to the same log file, gmlock.Lock(key) ensures the concurrent safe write operation of the file.

Posted by Spaceman-Spiff on Wed, 30 Jan 2019 13:57:14 -0800