最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

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

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

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

什么是可重入鎖

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

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

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

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

那么我們就一般在Lock的時(shí)候?yàn)檫@個(gè)鎖加一個(gè)超時(shí)時(shí)間作為兜底。

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

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

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

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

有兩個(gè)辦法:

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

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

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

具體實(shí)現(xiàn)

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

// DistributeLockRedis 基于redis的分布式可重入鎖,自動(dòng)續(xù)租
type DistributeLockRedis struct {
	key       string             // 鎖的key
	expire    int64              // 鎖超時(shí)時(shí)間
	status    bool               // 上鎖成功標(biāo)識(shí)
	cancelFun context.CancelFunc // 用于取消自動(dòng)續(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)建守護(hù)協(xié)程,自動(dòng)對(duì)鎖進(jìn)行續(xù)期
	dl.status = true
	return nil
}

// competition 競(jìng)爭(zhēng)鎖
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)建守護(hù)協(xié)程,自動(dòng)續(xù)期
func (dl *DistributeLockRedis) startWatchDog(ctx context.Context) {
	safeGo(func() error {
		for {
			select {
			// Unlock通知結(jié)束
			case <-ctx.Done():
				return nil
			default:
				// 否則只要開(kāi)始了,就自動(dòng)重入(續(xù)租鎖)
				if dl.status {
					if res, err := redis.Int(dl.redis.Do(context.Background(), "EXPIRE", dl.key, dl.expire)); err != nil {
						return nil
					} 
					// 續(xù)租時(shí)間為 expire/2 秒
					time.Sleep(time.Duration(dl.expire/2) * time.Second)
				}
			}
		}
	})
}

// Unlock 釋放鎖
func (dl *DistributeLockRedis) Unlock() (err error) {
	// 這個(gè)重入鎖必須取消,放在第一個(gè)地方執(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("釋放鎖失敗")
}

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

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

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

這個(gè)新啟動(dòng)的goroutine在主goroutine處理結(jié)束,調(diào)用Unlock的時(shí)候,才會(huì)結(jié)束,否則會(huì)在 過(guò)期時(shí)間/2 的時(shí)候,調(diào)用一次redis的expire命令來(lái)進(jìn)行續(xù)期。

至于外部,在使用的時(shí)候如下

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

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

相關(guān)文章

  • Go語(yǔ)言開(kāi)發(fā)發(fā)送Get和Post請(qǐng)求的示例

    Go語(yǔ)言開(kāi)發(fā)發(fā)送Get和Post請(qǐng)求的示例

    這篇文章主要介紹了Go語(yǔ)言開(kāi)發(fā)發(fā)送Get和Post請(qǐng)求的示例,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-07-07
  • Go語(yǔ)言輸出函數(shù)使用詳解

    Go語(yǔ)言輸出函數(shù)使用詳解

    這篇文章主要介紹了Go語(yǔ)言輸出函數(shù)使用詳解的相關(guān)資料,需要的朋友可以參考下
    2023-08-08
  • golang使用bcrypt包對(duì)密碼進(jìn)行加密的方法實(shí)現(xiàn)

    golang使用bcrypt包對(duì)密碼進(jìn)行加密的方法實(shí)現(xiàn)

    本文主要介紹了golang使用bcrypt包對(duì)密碼進(jìn)行加密的方法實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-07-07
  • Go語(yǔ)言Gin框架前后端分離項(xiàng)目開(kāi)發(fā)實(shí)例

    Go語(yǔ)言Gin框架前后端分離項(xiàng)目開(kāi)發(fā)實(shí)例

    本文主要介紹了Go語(yǔ)言Gin框架前后端分離項(xiàng)目開(kāi)發(fā)工程化實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-11-11
  • Hugo?Config模塊構(gòu)建實(shí)現(xiàn)源碼剖析

    Hugo?Config模塊構(gòu)建實(shí)現(xiàn)源碼剖析

    這篇文章主要為大家介紹了Hugo?Config模塊構(gòu)建實(shí)現(xiàn)源碼剖析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-02-02
  • golang DNS服務(wù)器的簡(jiǎn)單實(shí)現(xiàn)操作

    golang DNS服務(wù)器的簡(jiǎn)單實(shí)現(xiàn)操作

    這篇文章主要介紹了golang DNS服務(wù)器的簡(jiǎn)單實(shí)現(xiàn)操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2021-04-04
  • 詳解Golang如何優(yōu)雅接入多個(gè)遠(yuǎn)程配置中心

    詳解Golang如何優(yōu)雅接入多個(gè)遠(yuǎn)程配置中心

    這篇文章主要為大家為大家介紹了Golang如何優(yōu)雅接入多個(gè)遠(yuǎn)程配置中心詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-05-05
  • go語(yǔ)言中的return語(yǔ)句

    go語(yǔ)言中的return語(yǔ)句

    這篇文章主要介紹了go語(yǔ)言中的return語(yǔ)句,文章圍繞主題展開(kāi)詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下,希望對(duì)你的學(xué)習(xí)有所幫助
    2022-05-05
  • Go 簡(jiǎn)單實(shí)現(xiàn)多租戶(hù)數(shù)據(jù)庫(kù)隔離

    Go 簡(jiǎn)單實(shí)現(xiàn)多租戶(hù)數(shù)據(jù)庫(kù)隔離

    本文主要介紹了Go 簡(jiǎn)單實(shí)現(xiàn)多租戶(hù)數(shù)據(jù)庫(kù)隔離,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-05-05
  • Go微服務(wù)項(xiàng)目配置文件的定義和讀取示例詳解

    Go微服務(wù)項(xiàng)目配置文件的定義和讀取示例詳解

    這篇文章主要為大家介紹了Go微服務(wù)項(xiàng)目配置文件的定義和讀取示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-06-06

最新評(píng)論

新郑市| 英山县| 麻阳| 深州市| 绥化市| 星子县| 安丘市| 乡宁县| 金沙县| 芦溪县| 大名县| 商丘市| 区。| 万源市| 瑞安市| 涿鹿县| 噶尔县| 项城市| 遵化市| 永定县| 碌曲县| 鄄城县| 天峨县| 平凉市| 怀远县| 米脂县| 黑水县| 赤城县| 兰溪市| 荣成市| 桂东县| 揭西县| 高台县| 盐池县| 苗栗市| 沙河市| 宁武县| 公主岭市| 安义县| 长丰县| 青田县|