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

Go從defer關(guān)鍵字到鎖用法實(shí)例demo

 更新時(shí)間:2026年06月04日 08:31:42   作者:代碼N年歸來仍是新手村成員  
在Go語言中,defer語句用于延遲執(zhí)行一個(gè)函數(shù)或方法,直到包含它的函數(shù)執(zhí)行完畢時(shí)才執(zhí)行,這篇文章主要介紹了Go從defer關(guān)鍵字到鎖用法的相關(guān)資料,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下

學(xué)完了基礎(chǔ)的golang語法,就開始看工作中的項(xiàng)目了??吹揭粋€(gè)比較經(jīng)典常見的代碼塊,來理解defer感覺正好用。讓AI去除業(yè)務(wù)邏輯寫了一個(gè)demo,在此記錄一下

代碼片

package main

import (
	"fmt"
	"sync"
	"time"
)

var studentLocks = make(map[string]*sync.Mutex)
var lockForMap sync.Mutex

func getStudentLock(studentId string) *sync.Mutex {
	lockForMap.Lock()
	fmt.Printf("[%s] Locking map \n", studentId)

	defer func() {
		lockForMap.Unlock()
		fmt.Printf("[%s] Unlocking map \n", studentId)
	}()

	if _, ok := studentLocks[studentId]; !ok {
		studentLocks[studentId] = &sync.Mutex{}
	}

	return studentLocks[studentId]
}

func GetStudentData(studentId string, cache map[string]string) (string, error) {
	//cache check
	data, found := cache[studentId]

	if found {
		fmt.Printf("[%s]  Cache HIT \n", studentId)
		return data, nil
	}

	fmt.Printf("[%s]  Cache MISS. Preparing to lock... \n", studentId)

	//lock
	mutex := getStudentLock(studentId)
	fmt.Printf("[%s]  Acquiring lock... \n", studentId)
	mutex.Lock()

	defer func() {
		fmt.Printf("[%s]  Unlocking lock... \n", studentId)
		mutex.Unlock()
	}()

	fmt.Printf("[%s]  Lock acquired \n", studentId)

	//2nd cache check
	//another goroutine might have populated it while we were waiting for the lock
	data, found = cache[studentId]
	if found {
		fmt.Printf("[%s]  Double-Check Cache HIT \n", studentId)
		return data, nil
	}

	//fetch from storage
	fmt.Printf("[%s]  Double-Check Cache MISS. Preparing to lock... \n", studentId)
	time.Sleep(1 * time.Second)
	dataFromStorage := fmt.Sprintf("Data for %s from storage \n", studentId)

	//set to cache
	cache[studentId] = dataFromStorage
	fmt.Printf("[%s] Data stored in cache \n", studentId)
	return data, nil
}

func main() {
	sharedCache := make(map[string]string)

	var wg sync.WaitGroup
	wg.Add(2)

	go func() {
		defer wg.Done()
		GetStudentData("student-123", sharedCache)
	}()

	go func() {
		defer wg.Done()
		GetStudentData("student-123", sharedCache)
	}()

	wg.Wait()
}

執(zhí)行結(jié)果

 ./main                  
[student-123]  Cache MISS. Preparing to lock... 
[student-123] Locking map 
[student-123] Unlocking map 
[student-123]  Acquiring lock... 
[student-123]  Lock acquired 
[student-123]  DCL Cache MISS. Preparing to lock... 
[student-123]  Cache MISS. Preparing to lock... 
[student-123] Locking map 
[student-123] Unlocking map 
[student-123]  Acquiring lock... 
[student-123] Data stored in cache 
[student-123]  Unlocking lock... 
[student-123]  Lock acquired 
[student-123]  DCL Cache HIT 
[student-123]  Unlocking lock... 

defer 關(guān)鍵字

在以上代碼片中有多個(gè)defer 關(guān)鍵字,會(huì)發(fā)現(xiàn)它常常與鎖的lock綁定。在lock之后的unlock通常放到defer語句中。
unlock 邏輯放在defer 語句中,來確保無論func 如何退出,鎖都會(huì)釋放。類似的還有資源的關(guān)閉也會(huì)放在defer 中

defer 是在什么時(shí)候執(zhí)行的呢?注意在上面的代碼塊中,有兩個(gè)鎖,一個(gè)是鎖定lockMap的,另一個(gè)是鎖定一條緩存記錄的,這兩個(gè)鎖的上鎖和釋放都在defer 中,寫法是類似的。分別代表著方法成功執(zhí)行、方法失敗或者報(bào)錯(cuò)時(shí),鎖都被釋放。這樣寫原因是防止忘記所釋放而引起的內(nèi)存泄漏或死鎖。

defer 的作用域是方法,而不是代碼塊,這點(diǎn)很重要,有的時(shí)候它存在于{}包圍的代碼塊中,以往的Java經(jīng)歷讓我誤會(huì)defer 是退出代碼塊的,并不是,它像return一樣是屬于方法的。

還有一種情況,此時(shí)defer 放在一個(gè)if塊里,如果Cache HIT,沒有走到if 塊里去lock,那么unlock同樣也不會(huì)執(zhí)行,這就相當(dāng)于主函數(shù)壓根不會(huì)掛載一個(gè)defer 回調(diào),此時(shí)就不涉及defer 的執(zhí)行了。

func (){
if dataFromCache == nil{

	lock.Lock()
	//other logic...

	defer lock.Unlock()
}

//...

return data
}

番外:鎖與Double Check Locking

雖然這塊代碼是為了熟悉defer的作用,但是也是一個(gè)比較好的并發(fā)編程場(chǎng)景:先從緩存中查數(shù)據(jù),緩存命中則直接返回;緩存不存在則去數(shù)據(jù)庫里查,然后加載到緩存。

  1. 為什么加鎖
    在這個(gè)場(chǎng)景中,對(duì)數(shù)據(jù)以studentId為粒度加鎖(getStudentLock),為了防止并發(fā)對(duì)studentId數(shù)據(jù)的查詢。例如如果student-123在緩存中失效時(shí),同時(shí)有100個(gè)請(qǐng)求過來,此時(shí)100個(gè)請(qǐng)求都收到cache miss,并去DB中讀100次,浪費(fèi)資源,給數(shù)據(jù)庫壓力。
    加了這個(gè)鎖,保證只有一個(gè)請(qǐng)求可以獲取到鎖并到數(shù)據(jù)庫中加載數(shù)據(jù),其它99個(gè)請(qǐng)求等待。直到寫回緩存,鎖釋放。此時(shí)剩下的99個(gè)請(qǐng)求依次獲取鎖,可以再次檢查緩存,命中即可返回,不需要再查數(shù)據(jù)庫

  2. 為什么有兩把鎖
    以上代碼是有兩把鎖的,有一把mapLock是為了保護(hù)map的并發(fā)讀寫,要區(qū)分它studentId 鎖的區(qū)別,后者才是承擔(dān)了上面的功能。在實(shí)際的業(yè)務(wù)代碼中,緩存一般是Redis, 那么mapLock的邏輯實(shí)際上會(huì)封裝在Redis client的實(shí)現(xiàn)中,不需要手動(dòng)寫。

  3. 為什么有緩存的double-check
    其實(shí)這個(gè)代碼最開始是沒有2nd 檢查的邏輯的,當(dāng)執(zhí)行main 方法時(shí),會(huì)發(fā)現(xiàn)兩個(gè)student-123都從storage中獲取了,而我們想達(dá)到的目的是,只有一個(gè)從storage中獲取,加載到cache之后就從cache讀了。這就是因?yàn)楫?dāng)時(shí)沒有第二次緩存檢查,代碼如下。

mutex := getStudentLock(studentId)
	fmt.Printf("[%s]  Acquiring lock... \n", studentId)
	mutex.Lock()
	
	defer func() {
		fmt.Printf("[%s]  Unlocking lock... \n", studentId)
		mutex.Unlock()
	}()

	fmt.Printf("[%s]  Lock acquired \n", studentId)

	//fetch from storage
	fmt.Printf("[%s]  DCL Cache MISS. Preparing to lock... \n", studentId)
	time.Sleep(1 * time.Second)
	dataFromStorage := fmt.Sprintf("Data for %s from storage \n", studentId)

那么在高并發(fā)場(chǎng)景下,協(xié)程A獲取了鎖,另一個(gè)協(xié)程B此時(shí)獲取鎖失敗,等待。協(xié)程A從storage中讀到數(shù)據(jù),寫到緩存,釋放鎖。此時(shí)B獲取鎖,但注意,它會(huì)繼續(xù)執(zhí)行從storage中讀數(shù)據(jù),寫緩存,釋放鎖。這樣的結(jié)果是100個(gè)請(qǐng)求從并行改為線性了,數(shù)據(jù)庫的壓力緩解了,但是99個(gè)不必要的請(qǐng)求資源還是浪費(fèi)了。

加了double-check,就保證B在獲取鎖的時(shí)候先檢查緩存,緩存命中,那么就不需要再次去storage加載了。這就是Double-check的作用。

其實(shí)業(yè)務(wù)代碼里我也沒看到double-check,大概率是bug feature。因?yàn)镈CL這種模式,在高并發(fā)場(chǎng)景中是很有效的,但是在實(shí)際的業(yè)務(wù)中,并不會(huì)有同一個(gè)studentId 的并發(fā)訪問,所以沒有DCL就沒有了,多調(diào)用幾次storage問題也不大,查的也很快的,DCL不是必須的。能跑就行。

學(xué)習(xí)DCL是主要的,以后在高并發(fā)場(chǎng)景中可以考慮應(yīng)用。一個(gè)pattern,一定有它的業(yè)務(wù)場(chǎng)景的。如果是剛寫代碼的我,發(fā)現(xiàn)了問題,學(xué)了新東西,就想challenge別人,但有的時(shí)候code review也是人情世故。我真的是成熟的程序員了,哈哈

總結(jié) 

到此這篇關(guān)于Go從defer關(guān)鍵字到鎖用法的文章就介紹到這了,更多相關(guān)Go defer關(guān)鍵字到鎖內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Golang語言中切片的長(zhǎng)度和容量的概念和使用

    Golang語言中切片的長(zhǎng)度和容量的概念和使用

    這篇文章主要介紹了Golang語言中切片的長(zhǎng)度和容量的概念和使用,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2025-11-11
  • go將request?body綁定到不同的結(jié)構(gòu)體中教程

    go將request?body綁定到不同的結(jié)構(gòu)體中教程

    這篇文章主要為大家介紹了go將request?body綁定到不同的結(jié)構(gòu)體中教程,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-10-10
  • golang對(duì)自定義類型進(jìn)行排序的解決方法

    golang對(duì)自定義類型進(jìn)行排序的解決方法

    學(xué)習(xí)一門編程語言,要掌握原子數(shù)據(jù)類型,還需要掌握自定義數(shù)據(jù)類型。下面這篇文章主要給大家介紹了關(guān)于golang如何對(duì)自定義類型進(jìn)行排序的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考下。
    2017-12-12
  • Go高級(jí)特性探究之recover捕獲panic詳解

    Go高級(jí)特性探究之recover捕獲panic詳解

    在Go語言中,當(dāng)程序出現(xiàn)panic(即運(yùn)行時(shí)錯(cuò)誤)時(shí),程序會(huì)立即停止當(dāng)前的執(zhí)行流程,而recover函數(shù)的作用就是捕獲這個(gè)panic,下面就來看看具體是怎么操作的吧
    2023-06-06
  • go 迭代string數(shù)組操作 go for string[]

    go 迭代string數(shù)組操作 go for string[]

    這篇文章主要介紹了go 迭代string數(shù)組操作 go for string[],具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2020-12-12
  • go select的用法

    go select的用法

    本文主要介紹了go select的用法,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-01-01
  • Golang接口使用教程詳解

    Golang接口使用教程詳解

    在?Go?語言中接口包含兩種含義:它既是方法的集合,?同時(shí)還是一種類型并且在Go?語言中是隱式實(shí)現(xiàn)的。本文通過示例詳細(xì)介紹了Golang接口的使用,需要的可以參考一下
    2022-09-09
  • go語言make初始化的實(shí)現(xiàn)

    go語言make初始化的實(shí)現(xiàn)

    Go語言中的make函數(shù)用于初始化切片、映射和通道,本文就來介紹一下go語言make初始化的實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的可以了解一下
    2024-12-12
  • Go語言庫系列之dotsql詳解

    Go語言庫系列之dotsql詳解

    這篇文章主要介紹了Go語言庫系列之dotsql的相關(guān)知識(shí),本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-04-04
  • 探索Go語言中的switch高級(jí)用法

    探索Go語言中的switch高級(jí)用法

    在Go語言中,switch語句除了常見的用法外,還有一種不常用但有趣的寫法,這種寫法中,switch后面不跟任何表達(dá)式,而每個(gè)case后面跟的是返回bool類型的函數(shù)調(diào)用表達(dá)式,這實(shí)際上是一個(gè)等價(jià)于switch true的用法,通過從上到下逐一比較case后的表達(dá)式是否為true來決定執(zhí)行哪個(gè)分支
    2024-10-10

最新評(píng)論

博客| 河池市| 宜宾县| 兖州市| 琼中| 通化市| 平顶山市| 沧源| 区。| 苏尼特左旗| 长白| 昭平县| 荣昌县| 长葛市| 饶阳县| 佛学| 商洛市| 高唐县| 贡觉县| 文水县| 遵义县| 巴马| 蒙阴县| 越西县| 福海县| 新沂市| 嫩江县| 汝城县| 翁源县| 来宾市| 丰县| 黄浦区| 大石桥市| 广德县| 德江县| 伊金霍洛旗| 定陶县| 锡林郭勒盟| 清徐县| 盘锦市| 泸西县|