欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Golang實現(xiàn)可重入鎖的示例代碼

 更新時間:2022年05月25日 11:05:28   作者:葉劍峰  
可重入鎖指的是同一個線程外層函數(shù)獲得鎖之后,內(nèi)層遞歸函數(shù)仍然能獲取該鎖的代碼,在同一個線程在外層方法獲取鎖的時候,在進入內(nèi)層方法會自動獲取鎖。本文將用Golang實現(xiàn)可重入鎖,需要的可以參考一下

項目中遇到了可重入鎖的需求和實現(xiàn),具體記錄下。

什么是可重入鎖

我們平時說的分布式鎖,一般指的是在不同服務(wù)器上的多個線程中,只有一個線程能搶到一個鎖,從而執(zhí)行一個任務(wù)。而我們使用鎖就是保證一個任務(wù)只能由一個線程來完成。所以我們一般是使用這樣的三段式邏輯:

Lock();
DoJob();
Unlock();

但是由于我們的系統(tǒng)都是分布式的,這個鎖一般不會只放在某個進程中,我們會借用第三方存儲,比如 Redis 來做這種分布式鎖。但是一旦借助了第三方存儲,我們就必須面對這個問題:Unlock是否能保證一定運行呢?

這個問題,我們面對的除了程序的bug之外,還有網(wǎng)絡(luò)的不穩(wěn)定,進程被殺死,服務(wù)器被down機等。我們是無法保證Unlock一定被運行的。

那么我們就一般在Lock的時候為這個鎖加一個超時時間作為兜底。

LockByExpire(duration);
DoJob();
Unlock();

這個超時時間是為了一旦出現(xiàn)異常情況導(dǎo)致Unlock沒有被運行,這個鎖在duration時間內(nèi)也會被自動釋放。這個在redis中我們一般就是使用set ex 來進行鎖超時的設(shè)定。

但是有這個超時時間我們又遇上了問題,超時時間設(shè)置多久合適呢?當然要設(shè)置的比 DoJob 消耗的時間更長,否則的話,在任務(wù)還沒結(jié)束的時候,鎖就被釋放了,還是有可能導(dǎo)致并發(fā)任務(wù)的存在。

但是實際上,同樣由于網(wǎng)絡(luò)超時問題,系統(tǒng)運行狀況問題等,我們是無法準確知道DoJob這個函數(shù)要執(zhí)行多久的。那么這時候怎么辦呢?

有兩個辦法:

第一個方法,我們可以對DoJob做一個超時設(shè)置。讓DoJob最多只能執(zhí)行n秒,那么我的分布式鎖的超時時長設(shè)置比n秒長就可以了。為一個任務(wù)設(shè)置超時時間在很多語言是可以做到的。比如golang 中的 TimeoutContext。

而第二種方法,就是我們先為鎖設(shè)置一個比較小的超時時長,然后不斷續(xù)期這個鎖。對一個鎖的不斷需求,也可以理解為重新開始加鎖,這種可以不斷續(xù)期的鎖,就叫做可重入鎖。

除了主線程之外,可重入鎖必然有一個另外的線程(或者攜程)可以對這個鎖進行續(xù)期,我們叫這個額外的程序叫做watchDog(看門狗)。

具體實現(xiàn)

在Golang中,語言級別天生支持協(xié)程,所以這種可重入鎖就非常容易實現(xiàn):

// DistributeLockRedis 基于redis的分布式可重入鎖,自動續(xù)租
type DistributeLockRedis struct {
	key       string             // 鎖的key
	expire    int64              // 鎖超時時間
	status    bool               // 上鎖成功標識
	cancelFun context.CancelFunc // 用于取消自動續(xù)租攜程
	redis     redis.Client       // redis句柄
}

// 創(chuàng)建可
func NewDistributeLockRedis(key string, expire int64) *DistributeLockRedis {
	return &DistributeLockRedis{
		 key : key,
		 expire : expire,
	}
}

// TryLock 上鎖
func (dl *DistributeLockRedis) TryLock() (err error) {
	if err = dl.lock(); err != nil {
		return err
	}
	ctx, cancelFun := context.WithCancel(context.Background())
	dl.cancelFun = cancelFun
	dl.startWatchDog(ctx) // 創(chuàng)建守護協(xié)程,自動對鎖進行續(xù)期
	dl.status = true
	return nil
}

// competition 競爭鎖
func (dl *DistributeLockRedis) lock() error {
	if res, err := redis.String(dl.redis.Do(context.Background(), "SET", dl.key, 1, "NX", "EX", dl.expire)); err != nil {
		return err
	} 
	return nil
}


// guard 創(chuàng)建守護協(xié)程,自動續(xù)期
func (dl *DistributeLockRedis) startWatchDog(ctx context.Context) {
	safeGo(func() error {
		for {
			select {
			// Unlock通知結(jié)束
			case <-ctx.Done():
				return nil
			default:
				// 否則只要開始了,就自動重入(續(xù)租鎖)
				if dl.status {
					if res, err := redis.Int(dl.redis.Do(context.Background(), "EXPIRE", dl.key, dl.expire)); err != nil {
						return nil
					} 
					// 續(xù)租時間為 expire/2 秒
					time.Sleep(time.Duration(dl.expire/2) * time.Second)
				}
			}
		}
	})
}

// Unlock 釋放鎖
func (dl *DistributeLockRedis) Unlock() (err error) {
	// 這個重入鎖必須取消,放在第一個地方執(zhí)行
	if dl.cancelFun != nil {
		dl.cancelFun() // 釋放成功,取消重入鎖
	}
	var res int
	if dl.status {
		if res, err = redis.Int(dl.redis.Do(context.Background(), "Del", dl.key)); err != nil {
			return fmt.Errorf("釋放鎖失敗")
		}
		if res == 1 {
			dl.status = false
			return nil
		}
	}
	return fmt.Errorf("釋放鎖失敗")
}

這段代碼的邏輯基本上都以注釋的形式來寫了。其中主要就在startWatchDog,對鎖進行重新續(xù)期

ctx, cancelFun := context.WithCancel(context.Background())
dl.cancelFun = cancelFun
dl.startWatchDog(ctx) // 創(chuàng)建守護協(xié)程,自動對鎖進行續(xù)期
dl.status = true

首先創(chuàng)建一個cancelContext,它的context函數(shù)cancelFunc是給Unlock進行調(diào)用的。然后啟動一個goroutine進程來循環(huán)續(xù)期。

這個新啟動的goroutine在主goroutine處理結(jié)束,調(diào)用Unlock的時候,才會結(jié)束,否則會在 過期時間/2 的時候,調(diào)用一次redis的expire命令來進行續(xù)期。

至于外部,在使用的時候如下

func Foo() error {
  key := foo
  
  // 創(chuàng)建可重入的分布式鎖
	dl := NewDistributeLockRedis(key, 10)
	// 爭搶鎖
	err := dl.TryLock()
	if err != nil {
		// 沒有搶到鎖
		return err
	}
	
	// 搶到鎖的記得釋放鎖
	defer func() {
		dl.Unlock()
	}
	
	// 做真正的任務(wù)
	DoJob()
}

以上就是Golang實現(xiàn)可重入鎖的示例代碼的詳細內(nèi)容,更多關(guān)于Golang可重入鎖的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 在Golang代碼中如何自動生成版本號的方法示例

    在Golang代碼中如何自動生成版本號的方法示例

    這篇文章主要給大家介紹了在Golang代碼中如何自動生成版本號的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家具有一定的參考學習價值,需要的朋友們下面來一起看看吧。
    2017-07-07
  • Go語言中關(guān)閉帶緩沖區(qū)的頻道實例分析

    Go語言中關(guān)閉帶緩沖區(qū)的頻道實例分析

    這篇文章主要介紹了Go語言中關(guān)閉帶緩沖區(qū)的頻道,實例分析了帶緩沖區(qū)頻道的原理與用法,以及關(guān)閉帶緩沖區(qū)頻道的技巧,具有一定參考借鑒價值,需要的朋友可以參考下
    2015-02-02
  • Go語言CSP并發(fā)模型goroutine及channel底層實現(xiàn)原理

    Go語言CSP并發(fā)模型goroutine及channel底層實現(xiàn)原理

    這篇文章主要為大家介紹了Go語言CSP并發(fā)模型goroutine?channel底層實現(xiàn)原理,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-05-05
  • Golang 類型轉(zhuǎn)換的實現(xiàn)(斷言、強制、顯式類型)

    Golang 類型轉(zhuǎn)換的實現(xiàn)(斷言、強制、顯式類型)

    將一個值從一種類型轉(zhuǎn)換到另一種類型,便發(fā)生了類型轉(zhuǎn)換,在go可以分為斷言、強制、顯式類型轉(zhuǎn)換,本文就詳細的介紹一下這就幾種轉(zhuǎn)換方式,具有一定的參考價值,感興趣的可以了解一下
    2023-09-09
  • go語言實現(xiàn)一個簡單的http客戶端抓取遠程url的方法

    go語言實現(xiàn)一個簡單的http客戶端抓取遠程url的方法

    這篇文章主要介紹了go語言實現(xiàn)一個簡單的http客戶端抓取遠程url的方法,實例分析了Go語言http操作技巧,具有一定參考借鑒價值,需要的朋友可以參考下
    2015-03-03
  • GoLang函數(shù)棧的使用詳細講解

    GoLang函數(shù)棧的使用詳細講解

    這篇文章主要介紹了GoLang函數(shù)棧的使用,我們的代碼會被編譯成機器指令并寫入到可執(zhí)行文件,當程序執(zhí)行時,可執(zhí)行文件被加載到內(nèi)存,這些機器指令會被存儲到虛擬地址空間中的代碼段,在代碼段內(nèi)部,指令是低地址向高地址堆積的
    2023-02-02
  • Golang設(shè)計模式之單例模式詳細講解

    Golang設(shè)計模式之單例模式詳細講解

    單例模式很容易記住。就像名稱一樣,它只能提供對象的單一實例,保證一個類只有一個實例,并提供一個全局訪問該實例的方法。本文就來聊聊Go語言中的單例模式,感興趣的小伙伴可以了解一下
    2023-01-01
  • Go語言異常處理(Panic和recovering)用法詳解

    Go語言異常處理(Panic和recovering)用法詳解

    異常處理是程序健壯性的關(guān)鍵,往往開發(fā)人員的開發(fā)經(jīng)驗的多少從異常部分處理上就能得到體現(xiàn)。Go語言中沒有Try?Catch?Exception機制,但是提供了panic-and-recover機制,本文就來詳細講講他們的用法
    2022-07-07
  • go?defer延遲調(diào)用的使用場景示例詳解

    go?defer延遲調(diào)用的使用場景示例詳解

    這篇文章主要為大家介紹了go?defer延遲調(diào)用的使用場景示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-05-05
  • Golang切片連接成字符串的實現(xiàn)示例

    Golang切片連接成字符串的實現(xiàn)示例

    本文主要介紹了Golang切片連接成字符串的實現(xiàn)示例,可以使用Go語言中的內(nèi)置函數(shù)"String()"可以將字節(jié)切片轉(zhuǎn)換為字符串,具有一定的參考價值,感興趣的可以了解一下
    2023-11-11

最新評論