gf框架之gmlock - 內存鎖模塊

文章來源:http://gf.johng.cn/os/gmlock/...html

內存鎖。該模塊包含兩個對象特性:git

  1. Locker 內存鎖,支持按照給定鍵名生成內存鎖,並支持Try*Lock鎖過時特性;
  2. Mutex 對標準庫底層sync.Mutex的封裝,增長了Try*Lock特性;

使用方式安全

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

使用場景併發

  1. 任何須要併發安全的場景,能夠替代sync.Mutex
  2. 須要使用Try*Lock的場景(不須要阻塞等待鎖釋放);
  3. 須要動態建立互斥鎖,或者須要維護大量動態鎖的場景;

方法列表

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()

示例1,基本使用

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()
}

該示例中,模擬了同時開啓10個goroutine,但同一時刻只能有一個goroutine得到鎖,得到鎖的goroutine執行1秒後退出,其餘goroutine才能得到鎖。日誌

執行後,輸出結果爲:code

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

示例2,過時控制

咱們將以上的示例使用過時時間控制來實現。orm

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()
}

執行後,輸出結果爲:htm

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

示例3,TryLock非阻塞鎖

TryLock方法是有返回值的,它表示用來嘗試獲取鎖,若是獲取成功,則返回true;若是獲取失敗(即鎖已被其餘goroutine獲取),則返回false對象

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()
}

同理,在該示例中,同時也只有1個goroutine能得到鎖,其餘goroutine在TryLock失敗便直接退出了。進程

執行後,輸出結果爲:

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

示例4,多個鎖機制衝突

該示例用來演示在複雜邏輯下的鎖機制處理狀況。

package main

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

// 內存鎖 - 手動Unlock與計時Unlock衝突校驗
func main() {
    key := "key"

    // 第一次鎖帶時間
    gmlock.Lock(key, 1000)
    glog.Println("lock1")
    // 這個時候上一次的計時解鎖已失效
    gmlock.Unlock(key)
    glog.Println("unlock1")

    fmt.Println()

    // 第二次鎖,不帶時間,且在執行過程當中前一個Lock的定時解鎖生效
    gmlock.Lock(key)
    glog.Println("lock2")
    go func() {
        // 正常狀況下3秒後才能執行這句
        gmlock.Lock(key)
        glog.Println("lock by goroutine")
    }()
    time.Sleep(3*time.Second)
    // 這時再解鎖
    gmlock.Unlock(key)
    // 注意3秒以後纔會執行這一句
    glog.Println("unlock2")

    // 阻塞進程
    select{}
}

執行後,輸出結果爲:

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

示例5,多文件併發寫的安全控制

glog模塊寫日誌文件的時候有這麼一個核心方法,咱們拿來看一下(源代碼位於 /g/os/glog/glog_logger.go)。

// 這裏的寫鎖保證同一時刻只會寫入一行日誌,防止串日誌的狀況
func (l *Logger) print(std io.Writer, s string) {
    // 優先使用自定義的IO輸出
    if l.printHeader.Val() {
        s = l.format(s)
    }
    writer := l.GetWriter()
    if writer == nil {
        // 若是設置的writer爲空,那麼其次判斷是否有文件輸出設置
        // 內部使用了內存鎖,保證在glog中對同一個日誌文件的併發寫入不會串日誌(併發安全)
        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)
    }
    // 是否容許輸出到標準輸出
    if l.alsoStdPrint.Val() {
        l.doStdLockPrint(std, s)
    }
}

其中的:

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

便使用到了內存鎖的特性,其中的變量key表示的是日誌文件的絕對路徑,當多個goroutine對同一個日誌文件進行寫入時,由gmlock.Lock(key)來保證對該文件的併發安全寫操做。

相關文章
相關標籤/搜索