在线观看www成人影院-在线观看www日本免费网站-在线观看www视频-在线观看操-欧美18在线-欧美1级

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

Go語言sync包中的鎖都在什么場景下用

馬哥Linux運維 ? 來源:JWang的博客 ? 作者:JWang ? 2021-10-26 09:35 ? 次閱讀

Go 語言 sync 包中的鎖都在什么場景下用?怎么用?本文對 sync 包內的鎖做了梳理。

今天談一下鎖,以及 Go 里面 Sync 包里面自帶的各種鎖,說到鎖這個概念,在日常生活中,鎖是為了保護一些東西,比如門鎖、密碼箱鎖,可以理解對資源的保護。在編程里面,鎖也是為了保護資源,比如說對文件加鎖,同一時間只也許一個用戶修改,這種鎖一般叫作文件鎖。

實際開發中,鎖又可分為互斥鎖(排它鎖)、讀寫鎖、共享鎖、自旋鎖,甚至還有悲觀鎖、樂觀鎖這種說法。在 Mysql 數據庫里面鎖的應用更多,比如行鎖、表鎖、間隙鎖,有點眼花繚亂。拋開這些概念,在編程領域,鎖的本質是為了解決并發情況下對數據資源的訪問問題,如果我們不加鎖,并發讀寫一塊數據必然會產生問題,如果直接加個互斥鎖問題是解決了,但是會嚴重影響讀寫性能,所以后面又產生了更復雜的鎖機制,在數據安全性和性能之間找到最佳平衡點。

正常來說,只有在并發編程下才會需要鎖,比如說多個線程(在 Go 里面則是協程)同時讀寫一個文件,下面我以一個文件為例,來解釋這幾種鎖的概念:

如果我們使用互斥鎖,那么同一時間只能由一線程去操作(讀或寫),這就是像是咱們去上廁所,一個坑位同一時間只能蹲一個人,這就是廁所門鎖的作用。

如果我們使用讀寫鎖,意味著可以同時有多個線程讀取這個文件,但是寫的時候不能讀,并且只能由一個線程去寫。這個鎖實際上是互斥鎖的改進版,很多時候我們之所以給文件加鎖是為了避免你在寫的過程中有人讀到了臟數據。

如果我們使用共享鎖,根據我查到資料,這種叫法大多數是源自 MySQL 事務里面的鎖概念,它意味著只能讀數據,并不能修改數據。

如果我們使用自旋鎖,則意味著當一個線程在獲取鎖的時候,如果鎖已經被其它線程獲取,那么該線程將循環等待,然后不斷的判斷鎖是否能夠被成功獲取,直到獲取到鎖才會退出循環。

這些鎖的機制在 Go 里面有什么應用呢,下面大家一起看看 Go 標準庫里面 sync 包提供的一些非常強大的基于鎖的實現。

1. 文件鎖

文件鎖和 sync 包沒關系,這里面只是順便說一下,舉個例子,磁盤上面有一個文件,必須保證同一時間只能由一個人打開,這里的同一時間是指操作系統層面的,并不是指應用層面,文件鎖依賴于操作系統實現。

在 C 或 PHP 里面,文件鎖會使用一個 flock 的函數去實現,其實 Go 里面也類似:

funcmain(){
varf="/var/logs/app.log"
file,err:=os.OpenFile(f,os.O_RDWR,os.ModeExclusive)
iferr!=nil{
panic(err)
}
deferfile.Close()

//調用系統調用加鎖
err=syscall.Flock(int(file.Fd()),syscall.LOCK_EX|syscall.LOCK_NB)
iferr!=nil{
panic(err)
}
defersyscall.Flock(int(file.Fd()),syscall.LOCK_UN)
//讀取文件內容
all,err:=ioutil.ReadAll(file)
iferr!=nil{
panic(err)
}

fmt.Printf("%s",all)
time.Sleep(time.Second*10)//模擬耗時操作
}

需要說明一下,Flock 函數第一個參數是文件描述符,第二個參數是鎖的類型,分為 LOCK_EX(排它鎖)、LOCK_SH(讀共享鎖)、LOCK_NB(遭遇鎖的表現,遇到排它鎖的時候默認會被阻塞,NB 即非阻塞,直接返回 Error)、LOCK_UN(解鎖)。

如果這時候你打開另外一個終端再次運行這個程序你會發現報錯信息如下:

panic:resourcetemporarilyunavailable

文件鎖保證了一個文件在操作系統層面的數據讀寫安全,不過實際應用中并不常見,畢竟大部分時候我們都是使用數據庫去做數據存儲,極少使用文件。

2.sync.Mutex

下面我所說的這些鎖都是應用級別的鎖,位于 Go 標準庫 sync 包里面,各有各的應用場景。

這是一個標準的互斥鎖,平時用的也比較多,用法也非常簡單,lock 用于加鎖,unlock 用于解鎖,配合 defer 使用,完美。

為了更好的展示鎖的應用,這個舉一個沒有實際意義的例子,給一個 int 變量做加法,用 2 個協程并發的去做加法。

variint

funcmain(){
goadd(&i)

time.Sleep(time.Second*3)

println(i)
}

funcadd(i*int){
forj:=0;j10000;j++{
*i=*i+1
}
}

我們想要得到的正常結果是 20000,然而實際上并不是,其結果是不固定的,很可能少于 20000,大家多運行幾次便可得知。

假設你多加一行 runtime.GOMAXPROCS(1),你會發現結果一直是正確的,這是為什么呢?

用一個比較理論的說法,這是因為產生了數據競爭(data race)問題,在 Go 里面我們可以在 go run 后面加上-race檢測數據競爭,結果會告訴你在哪一行產生的,非常實用。

gorun-racemain.go
==================
WARNING:DATARACE
Readat0x00000056ccb8bygoroutine7:
main.add()
main.go:23+0x43
Previouswriteat0x00000056ccb8bygoroutine6:
main.add()
main.go:23+0x59
Goroutine7(running)createdat:
main.main()
main.go:14+0x76
Goroutine6(running)createdat:
main.main()
main.go:13+0x52
==================
20000
Found1datarace(s)
exitstatus66

解決這個問題,有多種解法,我們當然可以換個寫法,比如說用 chan 管道去做加法(chan 底層也用了鎖),實際上在 Go 里面更推薦去使用 chan 解決數據同步問題,而不是直接用鎖機制。

在上面的這個例子里面我們需要在 add 方法里面寫,每次操作之前 lock,然后 unlock:

funcadd(i*int){
forj:=0;j10000;j++{
s.Lock()
*i=*i+1
s.Unlock()
}
}

3.sync.RWMutex

讀寫鎖是互斥鎖的升級版,它最大的優點就是支持多讀,但是讀和寫、以及寫與寫之間還是互斥的,所以比較適合讀多寫少的場景。

它的實現里面有 5 個方式:

func(rw*RWMutex)Lock()
func(rw*RWMutex)RLock()
func(rw*RWMutex)RLocker()Locker
func(rw*RWMutex)RUnlock()
func(rw*RWMutex)Unlock()

其中 Lock() 和 Unlock() 用于申請和釋放寫鎖,RLock() 和 RUnlock() 用于申請和釋放讀鎖,RLocker() 用于返回一個實現了 Lock() 和 Unlock() 方法的 Locker 接口

實話說,平時這個用的真不多,主要是使用起來比較復雜,雖然在讀性能上面比Mutex要好一點。

4.sync.Map

這個類型印象中是后來加的,最早很多人使用互斥鎖來并發的操作 map,現在也還有人這么寫:

typeUserstruct{
mmap[string]string
lsync.Mutex
}

也就是一個 map 配一把鎖的寫法,可能是這種寫法比較多,于是乎官方就在標準庫里面實現了一個sync.Map, 是一個自帶鎖的 map,使用起來方便很多,省心。

varmsync.Map

funcmain(){
m.Store("1",1)
m.Store("2",1)
m.Store("3",1)
m.Store(4,"5")//注意類型

load,ok:=m.Load("1")
ifok{
fmt.Printf("%v
",load)
}

load,ok=m.Load(4)
ifok{
fmt.Printf("%v
",load)
}
}

需要注意的一點是這個 map 的 key 和 value 都是 interface{}類型,所以可以隨意放入任何類型的數據,在使用的時候就需要做好斷言處理。

5.sync.Once

packagemain

import"sync"

varoncesync.Once

funcmain(){
doOnce()
}

funcdoOnce(){
once.Do(func(){
println("one")
})
}

執行結果只打印了一個 one,所以 sync.Once 的功能就是保證只執行一次,也算是一種鎖,通常可以用于只能執行一次的初始化操作,比如說單例模式里面的懶漢模式可以用到。

6.sync.Cond

這個一般稱之為條件鎖,就是當滿足某些條件下才起作用的鎖,啥個意思呢?舉個例子,當我們執行某個操作需要先獲取鎖,但是這個鎖必須是由某個條件觸發的,其中包含三種方式:

等待通知:wait, 阻塞當前線程,直到收到該條件變量發來的通知

單發通知:signal, 讓該條件變量向至少一個正在等待它的通知的線程發送通知,表示共享數據的狀態已經改變

廣播通知:broadcast, 讓條件變量給正在等待它的通知的所有線程都發送通知

下面看一個簡單的例子:

packagemain
import(
"sync"
"time"
)

varcond=sync.NewCond(&sync.Mutex{})

funcmain(){
fori:=0;i10;i++{
gofunc(iint){
cond.L.Lock()
cond.Wait()//等待通知,阻塞當前goroutine
println(i)
cond.L.Unlock()
}(i)
}

//確保所有協程啟動完畢
time.Sleep(time.Second*1)

cond.Signal()

//確保結果有時間輸出
time.Sleep(time.Second*1)
}

開始我們使用 for 循環啟動 10 個協程,每個協程都在等待鎖,然后使用 signal 發送一個通知。

如果你多次運行,你會發現打印的結果也是隨機從 0 到 9,說明各個協程之間是競爭的,鎖是起到作用的。如果把 singal 替換成 broadcast,則會打印所有結果。

講實話,我暫時也沒有發現有哪些應用場景,感覺這個應該適合需要非常精細的協程控制場景,大家先了解一下吧。

7.sync.WaitGroup

這個大多數人都用過,一般用來控制協程執行順序,大家都知道如果我們直接用 go 啟動一個協程,比如下面這個寫法:

gofunc(){
println("1")
}()

time.Sleep(time.Second*1)//睡眠1s

如果沒有后面的 sleep 操作,協程就得不到執行,因為整個函數結束了,主進程都結束了協程哪有時間執行,所以有時候為了方便可以直接簡單粗暴的睡眠幾秒,但是實際應用中不可行。這時候就可以使用 waitGroup 解決這個問題,舉個例子:

packagemain

import"sync"

varwgsync.WaitGroup

funcmain(){
fori:=0;i10;i++{
wg.Add(1)//計數+1
gofunc(){
println("1")
wg.Done()//計數-1,相當于wg.add(-1)
}()
}
wg.Wait()//阻塞帶等待所有協程執行完畢
}

8.sync.Pool

這是一個池子,但是卻是一個不怎么可靠的池子,sync.Pool 初衷是用來保存和復用臨時對象,以減少內存分配,降低 CG 壓力。

說它不可靠是指放進 Pool 中的對象,會在說不準什么時候被 GC 回收掉,所以如果事先 Put 進去 100 個對象,下次 Get 的時候發現 Pool 是空也是有可能的。

packagemain

import(
"fmt"
"sync"
)

typeUserstruct{
namestring
}

varpool=sync.Pool{
New:func()interface{}{
returnUser{
name:"defaultname",
}
},
}

funcmain(){
pool.Put(User{name:"name1"})
pool.Put(User{name:"name2"})

fmt.Printf("%v
",pool.Get())//{name1}
fmt.Printf("%v
",pool.Get())//{name2}
fmt.Printf("%v
",pool.Get())//{defaultname}池子已空,會返回New的結果
}

從輸出結果可以看到,Pool 就像是一個池子,我們放進去什么東西,但不一定可以取出來(如果中間有 GC 的話就會被清空),如果池子空了,就會使用之前定義的 New 方法返回的結果。

為什么這個池子會放到 sync 包里面呢?那是因為它有一個重要的特性就是協程安全的,所以其底層自然也用到鎖機制。

至于其應用場景,知名的 Web 框架 Gin 里面就有用到,在處理用戶的每條請求時都會為當前請求創建一個上下文環境 Context,用于存儲請求信息及相應信息等。Context 滿足長生命周期的特點,且用戶請求也是屬于并發環境,所以對于線程安全的 Pool 非常適合用來維護 Context 的臨時對象池。

編輯:jq
聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • Web
    Web
    +關注

    關注

    2

    文章

    1265

    瀏覽量

    69526
  • 函數
    +關注

    關注

    3

    文章

    4338

    瀏覽量

    62738
  • go語言
    +關注

    關注

    1

    文章

    158

    瀏覽量

    9053

原文標題:淺談 Golang 鎖的應用: sync包

文章出處:【微信號:magedu-Linux,微信公眾號:馬哥Linux運維】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    THS8135不需要外部再引入SYNC/BLANK信號,M1/M2/CLK & SYNC/SYNC_T/BLANK信號應該怎樣處理?

    我們有如下應用,請教一再這種場景THS8135 的 M1/M2/SYNC/SYNC_T/BLANK pin如何設置(上下拉)。 將CV
    發表于 12-31 07:31

    DAC38RF84 SYNC信號上電就是高,為什么?

    在調試DAC38RF84時遇到了問題,芯片一上電,即使不配置DAC38RF84寄存器,甚至不供給時鐘,兩個通道的SYNC就是高。按我的理解,204B協議SYNC信號應該是接收方發送同步請求時
    發表于 12-13 08:38

    影目科技發布全球首款同傳翻譯眼鏡INMO GO2

    近日,搭載紫光展銳W517芯片平臺的INMO GO2由影目科技正式推出。作為全球首款專為商務場景設計的智能翻譯眼鏡,INMO GO2 以“快、準、穩”三大核心優勢,突破傳統翻譯產品局限,為全球商務人士帶來高效、自然、穩定的跨
    的頭像 發表于 12-11 10:00 ?229次閱讀

    dac8565引腳LDAC 與SYNC、RST.RSTSEL是什么作用?怎么

    最近在用dac8565片子遇到幾個問題請教一,引腳LDAC 與SYNC、RST.RSTSEL是什么作用怎么? 有沒有這款 芯片的驅動程序?
    發表于 12-05 07:33

    在學習go語言的過程踩過的坑

    作為一個5年的phper,這兩年公司和個人都在順應技術趨勢,新項目慢慢從php轉向了go語言,從2021年到現在,筆者手上也先后開發了兩個go項目。在學習
    的頭像 發表于 11-11 09:22 ?172次閱讀

    go語言如何解決并發問題

    GO快很多。現在工作也還是有一些老項目在使用PHP,但21年之后的新項目基本上就都是GO了。那為什么PHP那么香,還要轉戰使用GO呢,下
    的頭像 發表于 10-23 13:38 ?147次閱讀
    <b class='flag-5'>go</b><b class='flag-5'>語言</b>如何解決并發問題

    三十分鐘入門基礎Go Java小子版

    前言 Go語言定義 Go(又稱 Golang)是 Google 的 Robert Griesemer,Rob Pike 及 Ken Thompson 開發的一種靜態、強類型、編譯型語言
    的頭像 發表于 08-12 14:32 ?750次閱讀
    三十分鐘入門基礎<b class='flag-5'>Go</b> Java小子版

    存器電路的中間是什么元件

    存器電路概述 定義與功能 存器(Latch)是數字電路的一種基本存儲元件,用于存儲一個位(1或0)的狀態。它能夠在特定輸入脈沖電平作用改變狀態,并保持該狀態直到下一個脈沖電平到
    的頭像 發表于 07-23 11:29 ?385次閱讀

    自旋和互斥的使用場景是什么

    自旋和互斥是兩種常見的同步機制,它們在多線程編程中被廣泛使用。在本文中,我們將介紹自旋和互斥的使用場景,以及它們在不同
    的頭像 發表于 07-10 10:05 ?1044次閱讀

    請問STVP+COSMIC環境go to definition怎么

    STVP+COSMIC環境go to definition怎么? 我現在go to definition在一個宏定義的時候有效果,但是函數什么的沒用,是怎么回事呢,是不是工程里面
    發表于 05-11 06:11

    關于go接口類型的表示方法

    go是一個靜態性語言,每個變量都有靜態的類型,因此每個變量在編譯階段中有明確的變量類型,比如像:int、float32、MyType。
    的頭像 發表于 04-28 10:13 ?380次閱讀

    Go語言中的函數、方法與接口詳解

    Go 沒有類,不過可以為結構體類型定義方法。方法就是一類帶特殊的接收者參數的函數。方法接收者在它自己的參數列表內,位于 func 關鍵字和方法名之間。(非結構體類型也可以定義方法)
    的頭像 發表于 04-23 16:21 ?860次閱讀

    這個多語言包 怎么搜不到

    大家好,這個多語言包怎么搜不到 seven language lib 誰有離線不 感謝分享,
    發表于 03-24 10:06

    fpga的是什么編程語言 fpga什么語言開發

    fpga的是什么編程語言 FPGA(現場可編程邏輯門陣列)主要使用的編程語言是硬件描述語言(HDL)。在眾多的HDL,Verilog H
    的頭像 發表于 03-14 17:09 ?3550次閱讀

    Arduino IDE是否有與Xmc2Go兼容的LoRaWAN庫?

    我想問一 Arduino IDE 是否有與 Xmc2Go 兼容的 LoRaWAN 庫? 我正在嘗試使用連接到 Xmc2Go 的 RFM95W Lora 模塊通過 LoRaWAN
    發表于 02-27 06:05
    主站蜘蛛池模板: 日韩成人免费一级毛片| 天堂自拍| 91精品啪国产在线观看免费牛牛| 天天操天天插| 奇米色吧| 夜夜五月天| ass嫩美女胴体| 亚色在线| 男人天堂网址| 国产精品资源在线观看网站| 91夜夜操| 美女用手扒开尿口给男生桶爽| 在线观看视频色| 婷婷久| 欧美三级精品| 色噜噜狠狠狠综合曰曰曰| 黄网站色视频免费看无下截| 在线黄色免费观看| 天天精品视频在线观看资源| 欧美黄色片免费看| 国产乱通伦| 天堂资源地址在线| 波多野结衣一级特黄毛片| 国产骚b| 91亚色视频在线观看| 国产精品爱久久久久久久三级 | 凹凸福利视频导航| 国产精品黄页网站在线播放免费| 久久成人免费网站| bt天堂资源在线种子| 神马午夜51| 美国69bj| 一级日本高清视频免费观看| 日韩日韩| 爆操极品美女| 国产在线综合网| 清朝荒淫牲艳史在线播放| 精品一区亚洲| 色综合中文网| 国产精品久久久亚洲| 欧美一级看片a免费观看|