網站首頁 編程語言 正文
其實鎖這種東西,都能能不加就不加,鎖會導致程序一定程度上退回到串行化,進而降低效率。
首先,看一個案例,如果要實現一個計數器,并且是多個協程共同進行的,就會出現以下的情況:
package main import ( "fmt" "sync" ) func main() { numberFlag := 0 wg := new(sync.WaitGroup) for i := 0; i < 200; i++ { wg.Add(1) go func() { defer wg.Done() numberFlag++ }() } fmt.Println(numberFlag) wg.Wait() }
每次執行后的計數器結果都是不同的,這是因為計數器本身是被不同的協程搶著+1,會產生多個協程同時拿到numberFlag=N的情況。為了避免這種資源競爭,要對資源進行加鎖,使得同一時刻只有一個協程能對資源進行操控。
package main import ( "fmt" "sync" ) func main() { numberFlag := 0 myLock := make(chan struct{}, 1) wg := new(sync.WaitGroup) for i := 0; i < 200; i++ { wg.Add(1) go func() { defer func() { // 釋放鎖 <-myLock }() defer wg.Done() // 搶鎖 myLock <- struct{}{} numberFlag++ }() } wg.Wait() fmt.Println(numberFlag) }
但是這種鎖只能用于你自己的本地服務,一旦出現多服務,比如分布式,微服務,這樣的場景,這個鎖就沒啥用了,這就需要分布式鎖。
關于分布式鎖,一般的實現就是用redis或者zookeeper實現。redis比較方便的就是大部分的服務都會使用redis,無需額外安裝依賴,而zookeeper普通服務用的并不多,即使是kafka也在新版放棄了zookeeper。
zookeeper最大的好處就是可以通過心跳檢測客戶端的情況,進而避免重復得鎖的問題。
但是同時也產生了一些問題,這個心跳檢測多久一次,在心跳檢測的間隔如果出現了鎖超時的問題怎么辦,等等。
所以一些服務還是傾向于使用redis來實現分布式鎖。
package main import ( "fmt" "github.com/gomodule/redigo/redis" "go-face-test/redisTest/redisOne/redisConn" "sync" "time" ) func main() { // 分布式鎖 var LockName = "lockLock" // 十秒過期時間 var ExpirationTime = 10 wg := new(sync.WaitGroup) wg.Add(2) // 起兩個協程來模擬分布式服務的搶占 go handleBusiness(LockName, ExpirationTime, "A", wg) go handleBusiness(LockName, ExpirationTime, "B", wg) wg.Wait() } func handleBusiness(lockName string, ExpTime int, nowGroName string, wg *sync.WaitGroup) { // One服務獲取鎖是否存在 c := redisConn.Get() defer c.Close() for { isKeyExists, err := redis.Bool(c.Do("EXISTS", lockName)) if err != nil { fmt.Println("err while checking keys:", err) } else { fmt.Println(isKeyExists) } if isKeyExists { // 存在這把鎖,開始自旋等待 fmt.Println("當前協程為: " + nowGroName + " 沒搶到鎖……") //休息1s time.Sleep(time.Second) } else { // 設置一把鎖 // 值為1,過期時間為10秒 reply, err := c.Do("SET", lockName, 2, "EX", ExpTime, "NX") // 搶占失敗 if reply == nil { fmt.Println("當前協程為: " + nowGroName + " 搶占鎖失敗") continue } // 開始業務處理 fmt.Println("當前協程為: " + nowGroName + " 啊啊啊啊。這是一個業務處理,預計處理時間為 3s 。處理開始........") fmt.Println("當前協程為: " + nowGroName + " 距離處理完成還有---3s" + time.Now().Format("2006-01-02 15:04:05")) time.Sleep(time.Second) fmt.Println("當前協程為: " + nowGroName + " 距離處理完成還有---2s" + time.Now().Format("2006-01-02 15:04:05")) time.Sleep(time.Second) fmt.Println("當前協程為: " + nowGroName + " 距離處理完成還有---1s" + time.Now().Format("2006-01-02 15:04:05")) time.Sleep(time.Second) //業務結束,釋放鎖 _, err = c.Do("DEL", lockName) if err != nil { fmt.Println("err while deleting:", err) } break } } wg.Done() }
但是這個鎖明顯有問題:
第一,當A服務(本案例中其實是協程模擬的)拿到鎖之后,處理超時了,鎖還沒有釋放,就已經過期,過期后B服務就搶到了鎖,此時AB均認為自己拿到了鎖
第二,A服務按理說只能去掉自己的服務加上的鎖,如果不止是有AB兩個服務,有更多的服務,那么A如果出現處理較慢,鎖超時后,B服務搶到鎖,A又處理完成所有的事釋放了鎖,那其實是釋放掉了B的鎖。也就是說,釋放鎖的時候也必須判斷是否是自己的鎖
那么就得用redis的lua來保證原子性
package main import ( "fmt" "github.com/gomodule/redigo/redis" "go-face-test/redisTest/redisTwo/redisConn" "log" "math/rand" "strconv" "sync" "time" ) var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") var lockCommand = `if redis.call("GET", KEYS[1]) == ARGV[1] then redis.call("SET", KEYS[1], ARGV[1], "PX", ARGV[2] , "NX") return "OK" else return redis.call("SET", KEYS[1], ARGV[1], "NX", "PX", ARGV[2]) end` var delCommand = `if redis.call("GET", KEYS[1]) == ARGV[1] then return redis.call("DEL", KEYS[1]) else return 0 end` func main() { // 分布式鎖 var LockName = "lockLock" // 十秒過期時間 var ExpirationTime = 3 wg := new(sync.WaitGroup) wg.Add(2) // 起兩個協程來模擬分布式服務的搶占 go handleBusiness(LockName, ExpirationTime, "A", wg) go handleBusiness(LockName, ExpirationTime, "B", wg) wg.Wait() } func init() { rand.Seed(time.Now().UnixNano()) } func handleBusiness(lockName string, ExpTime int, nowGroName string, wg *sync.WaitGroup) { // One服務獲取鎖是否存在 c := redisConn.Get() defer c.Close() for { isKeyExists, err := redis.Bool(c.Do("EXISTS", lockName)) if err != nil { fmt.Println("err while checking keys:", err) } else { fmt.Println(isKeyExists) } if isKeyExists { // 存在這把鎖,開始自旋等待 fmt.Println("當前協程為: " + nowGroName + " 沒搶到鎖……") //休息1s time.Sleep(time.Second) } else { // 設置一把鎖 // 鎖的值是根據當前服務名稱和時間來的 lockFlag, lockValue, _ := getLock(lockName, nowGroName, ExpTime, c) // 搶占失敗 if !lockFlag { fmt.Println("當前協程為: " + nowGroName + " 搶占鎖失敗") continue } // 開始業務處理 fmt.Println("當前協程為: " + nowGroName + " 啊啊啊啊。這是一個業務處理,預計處理時間為 " + strconv.Itoa(ExpTime) + "s 。處理開始........") for i := ExpTime - 1; i > 0; i-- { fmt.Println("當前協程為: " + nowGroName + " 距離處理完成還有---" + strconv.Itoa(i) + "s " + time.Now().Format("2006-01-02 15:04:05")) time.Sleep(time.Second) } //業務結束,釋放鎖 lockDelFlag, _ := delLock(lockName, lockValue, c) //獲取當前鎖的值 if lockDelFlag { fmt.Println("釋放鎖成功") } else { fmt.Println("這個鎖不是你的,或者這個鎖已經超時") } break } } wg.Done() } // 獲得唯一鎖的值 func getLockOnlyValue(nowGroName string) string { nano := strconv.FormatInt(time.Now().UnixNano(), 10) return nowGroName + "_" + nano + "_" + RandStringRunes(6) } // 獲得一個鎖 func getLock(LockName, nowGroName string, timeOut int, conn redis.Conn) (bool, string, error) { myLockValue := getLockOnlyValue(nowGroName) lua := redis.NewScript(1, lockCommand) resp, err := lua.Do(conn, LockName, myLockValue, strconv.Itoa(timeOut*1000)) if err != nil { log.Fatal(LockName, err) return false, "", err } else if resp == nil { return false, "", nil } s, ok := resp.(string) if !ok { return false, "", nil } if s != "OK" { return false, "", nil } return true, myLockValue, nil } // 刪除一個鎖 func delLock(LockName, LockeValue string, conn redis.Conn) (bool, error) { lua := redis.NewScript(1, delCommand) resp, err := lua.Do(conn, LockName, LockeValue) if err != nil { return false, err } reply, ok := resp.(int64) if !ok { return false, nil } return reply == 1, nil } func RandStringRunes(n int) string { b := make([]rune, n) for i := range b { b[i] = letterRunes[rand.Intn(len(letterRunes))] } return string(b) }
原文鏈接:https://blog.csdn.net/kina100/article/details/126877730
相關推薦
- 2022-05-25 從零搭建開發腳手架 SpringBoot自定義配置的多種姿勢及Idea自動提示
- 2022-04-28 Python?常用的print輸出函數和input輸入函數_python
- 2022-06-22 Docker中?container?和?image?的命名_docker
- 2023-01-12 Python讀取mat(matlab數據文件)并實現畫圖_python
- 2022-06-12 C語言詳解熱門考點結構體內存對齊_C 語言
- 2023-04-02 使用C#連接SQL?Server的詳細圖文教程_C#教程
- 2023-02-10 Python常見錯誤:IndexError:?list?index?out?of?range解決_p
- 2022-05-12 解決終端關閉導致workerman關閉 終端關閉導致服務關閉
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支