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

淺析GO并發(fā)處理選擇sync還是channel

 更新時(shí)間:2023年08月29日 16:10:33   作者:阿兵云原生  
這篇文章主要想來和大家討論一下,GO?語言處理并發(fā)的時(shí)候我們是選擇sync還是channel,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解下

如何選擇 sync 和 channel

以前寫 C 的時(shí)候,我們一般是都通過共享內(nèi)存來通信,對于并發(fā)去操作某一塊數(shù)據(jù)時(shí),為了保證數(shù)據(jù)安全,控制線程間同步,我們們會(huì)去使用互斥鎖,加鎖解鎖來進(jìn)行處理

然而 GO 語言中建議的時(shí)候通過通信來共享內(nèi)存,使用 channel 來完成臨界區(qū)的同步機(jī)制

可是 GO 語言中的 channel 畢竟是屬于比較高級(jí)的原語,自然在性能上就比不上 sync包里面的鎖機(jī)制,感興趣的同學(xué)可以自己寫一個(gè)簡單的基準(zhǔn)測試來確認(rèn)一下效果,評(píng)論去可以交流

另外,使用 sync 包來控制同步時(shí),我們不會(huì)失去結(jié)構(gòu)對象的所有權(quán),還能讓多個(gè)協(xié)程之間同步訪問臨界區(qū)的資源,那么如果我們的需求能夠符合這種情況時(shí),還是建議使用 sync 包來控制同步更加的合理和高效

為什么會(huì)選擇使用 sync 包來控制同步結(jié)論:

  • 不期望失去結(jié)構(gòu)的控制權(quán)的同時(shí),還期望多個(gè)協(xié)程能夠安全的同步訪問臨界區(qū)資源
  • 對性能要求會(huì)更高的情況

sync 的 Mutex 和 RWMutex

查看 sync 包的源碼(xxx\Go\src\sync),我們可以看到 sync 包下面有如下幾個(gè)結(jié)構(gòu):

  • Mutex
  • RWMutex
  • Once
  • Cond
  • Pool
  • atomic 包原子操作

上述經(jīng)常使用的就是 Mutex 了,尤其是最開始不善于使用 channel 的時(shí)候,覺得使用 Mutex 非常的順手,其次 RWMutex 相對來說就會(huì)用的少一些

不知大家有沒有關(guān)注過,使用 Mutex 和 使用 RWMutex 的性能表現(xiàn),獲取大部分人都是默認(rèn)使用互斥鎖,一起寫個(gè) demo 來看看 他倆的性能對比

var (
        mu   sync.Mutex
        murw sync.RWMutex
        tt1  = 1
        tt2  = 2
        tt3  = 3
)
// 使用 Mutex 控制讀取數(shù)據(jù)
func BenchmarkReadMutex(b *testing.B) {
        b.RunParallel(func(pp *testing.PB) {
                for pp.Next() {
                        mu.Lock()
                        _ = tt1
                        mu.Unlock()
                }
        })
}
// 使用 RWMutex 控制讀取數(shù)據(jù)
func BenchmarkReadRWMutex(b *testing.B) {
        b.RunParallel(func(pp *testing.PB) {
                for pp.Next() {
                        murw.RLock()
                        _ = tt2
                        murw.RUnlock()
                }
        })
}
// 使用 RWMutex 控制讀寫入數(shù)據(jù)
func BenchmarkWriteRWMutex(b *testing.B) {
        b.RunParallel(func(pp *testing.PB) {
                for pp.Next() {
                        murw.Lock()
                        tt3++
                        murw.Unlock()
                }
        })
}

寫了三個(gè)簡單的基準(zhǔn)測試

  • 使用互斥鎖讀取數(shù)據(jù)
  • 使用讀寫鎖的讀鎖讀取數(shù)據(jù)
  • 使用讀寫鎖讀取和寫入數(shù)據(jù)
$ go test -bench . bbb_test.go --cpu 2
goos: windows
goarch: amd64
cpu: Intel(R) Core(TM)2 Duo CPU     T7700  @ 2.40GHz
BenchmarkReadMutex-2            39638757                30.45 ns/op
BenchmarkReadRWMutex-2          43082371                26.97 ns/op
BenchmarkWriteRWMutex-2         16383997                71.35 ns/op
$ go test -bench . bbb_test.go --cpu 4
goos: windows
goarch: amd64
cpu: Intel(R) Core(TM)2 Duo CPU     T7700  @ 2.40GHz
BenchmarkReadMutex-4            17066666                73.47 ns/op
BenchmarkReadRWMutex-4          43885633                30.33 ns/op
BenchmarkWriteRWMutex-4         10593098               110.3 ns/op
$ go test -bench . bbb_test.go --cpu 8
goos: windows
goarch: amd64
cpu: Intel(R) Core(TM)2 Duo CPU     T7700  @ 2.40GHz
BenchmarkReadMutex-8             8969340               129.0 ns/op
BenchmarkReadRWMutex-8          36451077                33.46 ns/op
BenchmarkWriteRWMutex-8          7728303               158.5 ns/op
$ go test -bench . bbb_test.go --cpu 16
goos: windows
goarch: amd64
cpu: Intel(R) Core(TM)2 Duo CPU     T7700  @ 2.40GHz
BenchmarkReadMutex-16            8533333               132.6 ns/op
BenchmarkReadRWMutex-16         39638757                29.98 ns/op
BenchmarkWriteRWMutex-16         6751646               173.9 ns/op
$ go test -bench . bbb_test.go --cpu 128
goos: windows
goarch: amd64
cpu: Intel(R) Core(TM)2 Duo CPU     T7700  @ 2.40GHz
BenchmarkReadMutex-128          10155368               116.0 ns/op
BenchmarkReadRWMutex-128        35108558                33.27 ns/op
BenchmarkWriteRWMutex-128        6334021               195.3 ns/op

可以看出來當(dāng)并發(fā)較小的時(shí)候,使用互斥鎖和使用讀寫鎖的讀鎖性能類似,當(dāng)并發(fā)逐漸變大時(shí),讀寫鎖的讀鎖性能并未發(fā)生較大變化,互斥鎖和讀寫鎖的性能都會(huì)隨著并發(fā)的變大而下降

那么很明顯,讀寫鎖適用于讀多寫少的場景,在大并發(fā)讀書數(shù)據(jù)的時(shí)候,多個(gè)協(xié)程可以同時(shí)拿到讀鎖,減少鎖競爭和等待時(shí)間

而互斥鎖并發(fā)的時(shí)候,多個(gè)協(xié)程中,只有一個(gè)協(xié)程能拿到鎖,其他協(xié)程就會(huì)阻塞和等待,影響性能

舉個(gè)例子,我們正常使用互斥鎖,看看可能會(huì)出現(xiàn)什么樣的問題

使用 sync 需要注意的地方

平時(shí)使用 sync 包中的鎖的時(shí)候,需要注意的是不要去拷貝已經(jīng)已經(jīng)使用過的 Mutex 或者是 RWMutex

寫一個(gè)簡單的 demo:

var mu sync.Mutex
// sync 的互斥鎖,讀寫鎖,在被使用之后,就不要去復(fù)制這個(gè)對象,若要復(fù)制,需要在其未被使用的時(shí)候
func main() {
    go func(mm sync.Mutex) {
            for {
                    mm.Lock()
                    time.Sleep(time.Second * 1)
                    fmt.Println("g2")
                    mm.Unlock()
            }
    }(mu)
    mu.Lock()
    go func(mm sync.Mutex) {
            for {
                    mm.Lock()
                    time.Sleep(time.Second * 1)
                    fmt.Println("g3")
                    mm.Unlock()
            }
    }(mu)
    time.Sleep(time.Second * 1)
    fmt.Println("g1")
    mu.Unlock()
    time.Sleep(time.Second * 20)
}

感興趣的朋友的,可以運(yùn)行一下,可以看到打印的結(jié)果中時(shí)沒有 g3 的,因此 g3 所在的協(xié)程已經(jīng)發(fā)生了死鎖,沒有機(jī)會(huì)去調(diào)用 unlock

出現(xiàn)這種情況的原因是這樣的,先來看看 Mutex 的內(nèi)部結(jié)構(gòu):

//...
// A Mutex must not be copied after first use.
//...
type Mutex struct {
        state int32
        sema  uint32
}

因?yàn)槔?Mutex 中的內(nèi)部結(jié)構(gòu)是有一個(gè) state (表示互斥鎖的狀態(tài))和 sema(表示控制互斥鎖的信號(hào)量),其中初始化 Mutex 的時(shí)候,他們都是 0,但是當(dāng)我們用 Mutex 加鎖時(shí),Mutex 的狀態(tài)就變成了 Locked 的狀態(tài),這個(gè)時(shí)候,其中一個(gè)協(xié)程去拷貝這個(gè) Mutex,并在自己協(xié)程中加鎖,就會(huì)出現(xiàn)死鎖的情況,這一點(diǎn)是非常需要注意的

如果涉及到這種多個(gè)協(xié)程使用 Mutex 的情況, 可以使用閉包或者傳入包裹鎖的結(jié)構(gòu)地址或者指針,這樣就可以避免使用鎖的時(shí)候?qū)е虏豢深A(yù)期的結(jié)果,避免一臉蒙圈

sync.Once

sync 包中的其他成員,不知 xdm 使用的多么,相對使用頻率較高的應(yīng)該就是 sync.Once 了,其他成員 xdm 可以自行看看源碼,或者評(píng)論區(qū)留言哦,我們來看看 syn.Once 如何使用,都有哪些需要注意的?

還記得之前寫 C 或者 C++ 的時(shí)候,對于程序生命周期只有一個(gè)實(shí)例的時(shí)候,我們會(huì)選擇使用單例模式來進(jìn)行處理,那么此處的 sync.Once 就是非常適合用在單例模式中

sync.Once 可以保證任意一個(gè)函數(shù)在程序運(yùn)行期間只被執(zhí)行一次,這一點(diǎn)相對來說就比每個(gè)包中的 init 函數(shù)靈活一些了

這里需要注意,sync.Once 中執(zhí)行的函數(shù),如果出現(xiàn)了 panic ,也是會(huì)被認(rèn)為是執(zhí)行完了了一次,之后如果再有邏輯需要進(jìn)入 sync.Once 是無法進(jìn)入并執(zhí)行函數(shù)邏輯的

一般情況下, sync.Once 用于對象資源的初始化和清理動(dòng)作,避免重復(fù)操作,可以來看一個(gè) demo:

  • 主函數(shù)開辟 3 個(gè)協(xié)程,且使用 sync.WaitGroup 來管控并等待子協(xié)程退出
  • 主函數(shù)開辟所有協(xié)程之后等待 2 秒,開始創(chuàng)建并獲取實(shí)例
  • 協(xié)程中也在獲取實(shí)例
  • 只要有一個(gè)協(xié)程獲取到進(jìn)入 Once,執(zhí)行邏輯之后,會(huì)出現(xiàn) panic
  • 出現(xiàn) panic 的協(xié)程捕獲了異常,此時(shí)全局的 instance 已經(jīng)被初始化,其他協(xié)程仍然無法進(jìn)入 Once 內(nèi)的函數(shù)
type Instance struct {
        Name string
}
var instance *Instance
var on sync.Once
func GetInstance(num int) *Instance {
        defer func() {
                if err := recover(); err != nil {
                        fmt.Println("num %d ,get instance and catch error ... \n", num)
                }
        }()
        on.Do(func() {
                instance = &Instance{Name: "阿兵云原生"}
                fmt.Printf("%d enter once ... \n", num)
                panic("panic....")
        })
        return instance
}
func main() {
        var wg sync.WaitGroup
        for i := 0; i < 3; i++ {
                wg.Add(1)
                go func(i int) {
                        ins := GetInstance(i)
                        fmt.Printf("%d: ins:%+v  , p=%p\n", i, ins, ins)
                        wg.Done()
                }(i)
        }
        time.Sleep(time.Second * 2)
        ins := GetInstance(9)
        fmt.Printf("9: ins:%+v  , p=%p\n", ins, ins)
        wg.Wait()
}

通過打印結(jié)果可以看出,0 對應(yīng)的協(xié)程進(jìn)入了 Once,且發(fā)生了 panic,因此當(dāng)前協(xié)程獲取到的 GetInstance 函數(shù)的結(jié)果是 nil

其他的協(xié)程包括主協(xié)程調(diào)用 GetInstance 函數(shù)都能正常拿到 instance 的地址,可以看出地址是同一個(gè),全局就只初始化了一次

$ go run main.go
0 enter once ...
num %d ,get instance and catch error ...
 0
0: ins:<nil>  , p=0x0
1: ins:&{Name:阿兵云原生}  , p=0xc000086000
2: ins:&{Name:阿兵云原生}  , p=0xc000086000
9: ins:&{Name:阿兵云原生}  , p=0xc000086000

到此這篇關(guān)于淺析GO并發(fā)處理選擇sync還是channel的文章就介紹到這了,更多相關(guān)go并發(fā)處理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • GO語言類型轉(zhuǎn)換和類型斷言實(shí)例分析

    GO語言類型轉(zhuǎn)換和類型斷言實(shí)例分析

    這篇文章主要介紹了GO語言類型轉(zhuǎn)換和類型斷言,以實(shí)例形式詳細(xì)分析了類型轉(zhuǎn)換和類型斷言的概念與使用技巧,需要的朋友可以參考下
    2015-01-01
  • Go工具鏈之go tool cover使用方法和示例詳解

    Go工具鏈之go tool cover使用方法和示例詳解

    go tool cover是Go工具鏈中的一個(gè)命令,作用是分析測試用例的代碼覆蓋率,本文將對go tool cover 作用,使用方法和使用場景作一個(gè)簡單的介紹,感興趣的同學(xué)可以參考閱讀一下
    2023-07-07
  • Golang import本地包和導(dǎo)入問題相關(guān)詳解

    Golang import本地包和導(dǎo)入問題相關(guān)詳解

    這篇文章主要介紹了Golang import本地包和導(dǎo)入問題相關(guān)詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-02-02
  • go語言中的指針自動(dòng)解引用

    go語言中的指針自動(dòng)解引用

    Go語言中,編譯器會(huì)自動(dòng)解引用指針來訪問字段,自動(dòng)解引用使得使用指針訪問結(jié)構(gòu)體字段和方法變得更加直觀,降低了編程錯(cuò)誤的風(fēng)險(xiǎn),并使代碼更易于理解和維護(hù)
    2024-10-10
  • 完美解決go Fscanf 在讀取文件時(shí)出現(xiàn)的問題

    完美解決go Fscanf 在讀取文件時(shí)出現(xiàn)的問題

    這篇文章主要介紹了完美解決go Fscanf 在讀取文件時(shí)出現(xiàn)的問題,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-03-03
  • Go計(jì)時(shí)器的示例代碼

    Go計(jì)時(shí)器的示例代碼

    定時(shí)器是任何編程語言的重要工具,它允許開發(fā)人員在特定時(shí)間間隔安排任務(wù)或執(zhí)行代碼,本文主要介紹了Go計(jì)時(shí)器的示例代碼,具有一定的參考價(jià)值,感興趣的可以了解一下
    2024-01-01
  • Go項(xiàng)目配置管理神器之viper的介紹與使用詳解

    Go項(xiàng)目配置管理神器之viper的介紹與使用詳解

    viper是一個(gè)完整的?Go應(yīng)用程序的配置解決方案,它被設(shè)計(jì)為在應(yīng)用程序中工作,并能處理所有類型的配置需求和格式,下面這篇文章主要給大家介紹了關(guān)于Go項(xiàng)目配置管理神器之viper的介紹與使用,需要的朋友可以參考下
    2023-02-02
  • 源碼剖析Golang中map擴(kuò)容底層的實(shí)現(xiàn)

    源碼剖析Golang中map擴(kuò)容底層的實(shí)現(xiàn)

    之前的文章詳細(xì)介紹過Go切片和map的基本使用,以及切片的擴(kuò)容機(jī)制。本文針對map的擴(kuò)容,會(huì)從源碼的角度全面的剖析一下map擴(kuò)容的底層實(shí)現(xiàn),需要的可以參考一下
    2023-03-03
  • golang協(xié)程關(guān)閉踩坑實(shí)戰(zhàn)記錄

    golang協(xié)程關(guān)閉踩坑實(shí)戰(zhàn)記錄

    協(xié)程(coroutine)是Go語言中的輕量級(jí)線程實(shí)現(xiàn),下面這篇文章主要給大家介紹了關(guān)于golang協(xié)程關(guān)閉踩坑的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2023-03-03
  • golang中的string與其他格式數(shù)據(jù)的轉(zhuǎn)換方法詳解

    golang中的string與其他格式數(shù)據(jù)的轉(zhuǎn)換方法詳解

    這篇文章主要介紹了golang中的string與其他格式數(shù)據(jù)的轉(zhuǎn)換方法,文章通過代碼示例介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下
    2023-10-10

最新評(píng)論