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

Go多線程中數(shù)據(jù)不一致問(wèn)題的解決方案(sync鎖機(jī)制)

 更新時(shí)間:2024年10月11日 11:01:13   作者:景天科技苑  
在Go語(yǔ)言的并發(fā)編程中,如何確保多個(gè)goroutine安全地訪問(wèn)共享資源是一個(gè)關(guān)鍵問(wèn)題,Go語(yǔ)言提供了sync包,其中包含了多種同步原語(yǔ),用于解決并發(fā)編程中的同步問(wèn)題,本文將詳細(xì)介紹sync包中的鎖機(jī)制,需要的朋友可以參考下

Go語(yǔ)言中的Sync鎖

在Go語(yǔ)言的并發(fā)編程中,如何確保多個(gè)goroutine安全地訪問(wèn)共享資源是一個(gè)關(guān)鍵問(wèn)題。Go語(yǔ)言提供了sync包,其中包含了多種同步原語(yǔ),用于解決并發(fā)編程中的同步問(wèn)題。本文將詳細(xì)介紹sync包中的鎖機(jī)制,并結(jié)合實(shí)際案例,幫助讀者理解和使用這些鎖。

要想解決臨界資源安全的問(wèn)題,很多編程語(yǔ)言的解決方案都是同步。
通過(guò)上鎖的方式,某一時(shí)間段,只能允許一個(gè)goroutine來(lái)訪問(wèn)這個(gè)共享數(shù)據(jù),當(dāng)前goroutine訪問(wèn)完畢, 解鎖后,其他的goroutine才 能來(lái)訪問(wèn)。

我們可以借助于sync包下的鎖操作。 synchronization

但是實(shí)際上,在Go的并發(fā)編程中有一句很經(jīng)典的話:不要以共享內(nèi)存的方式去通信:鎖,而要以通信的方式去共享內(nèi)存。

共享內(nèi)存的方式

鎖:多個(gè)線程拿的是同一個(gè)鑰匙,go語(yǔ)言不建議使用鎖機(jī)制來(lái)解決。不要以共享內(nèi)存的方式去通信

而要以通信的方式去共享內(nèi)存 go語(yǔ)言更建議我們使用 chan(通道) 來(lái)解決安全問(wèn)題。(后面會(huì)學(xué))

在Go語(yǔ)言中并不鼓勵(lì)用鎖保護(hù)共享狀態(tài)的方式,在不同的Goroutine中分享信息(以共享內(nèi)存的方式去通信)。
而是鼓勵(lì)通過(guò)channeI將共享狀態(tài)或共享狀態(tài)的變化在各個(gè)Goroutine之間傳遞(以通信的方式去共享內(nèi)存),這樣同樣能像用鎖一樣保證在同一的時(shí)間只有一個(gè)Goroutine訪問(wèn)共享狀態(tài)。

當(dāng)然,在主流的編程語(yǔ)言中為了保證多線程之間共享數(shù)據(jù)安全性和一致性,都會(huì)提供一套基本的同步工具集,如鎖,條件變量,原子操作等等。

Go語(yǔ)言標(biāo)準(zhǔn)庫(kù)也毫不意外的提供了這些同步機(jī)制,使用方式也和其他語(yǔ)言也差不多

一、互斥鎖(Mutex)

互斥鎖(sync.Mutex)是最基本的同步機(jī)制之一,用于確保同一時(shí)間只有一個(gè)goroutine能夠訪問(wèn)特定的資源。
當(dāng)一個(gè)goroutine持有互斥鎖時(shí),其他試圖獲取該鎖的goroutine將會(huì)被阻塞,直到鎖被釋放。

1.1 基本用法

package main

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

// 定義全局變量 票庫(kù)存為10張
var tickets int = 10

// 定義一個(gè)鎖  Mutex 鎖頭
var mutex sync.Mutex

func main() {
    go saleTicket("張三")
    go saleTicket("李四")
    go saleTicket("王五")
    go saleTicket("趙六")

    time.Sleep(time.Second * 5)
}

// 售票函數(shù)
func saleTicket(name string) {
    for {
        // 在拿到共享資源之前先上鎖
        mutex.Lock()
        if tickets > 0 {
            time.Sleep(time.Millisecond * 1)
            fmt.Println(name, "剩余票的數(shù)量為:", tickets)
            tickets--
        } else {
            // 票賣完,解鎖
            mutex.Unlock()
            fmt.Println("票已售完")
            break
        }
        // 操作完畢后,解鎖
        mutex.Unlock()
    }
}

上鎖之后,就不會(huì)出現(xiàn)問(wèn)題了

在這里插入圖片描述

1.2 使用sync.WaitGroup等待一組Goroutine完成

sync.WaitGroup類型可以用來(lái)等待一組Goroutine完成。例如:

package main

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

// waitgroup、

var wg sync.WaitGroup

func main() {
    // 公司最后關(guān)門的人   0
    // wg.Add(2) wg.Add(2)來(lái)告訴WaitGroup我們要等待兩個(gè)Goroutine完成  開啟幾個(gè)協(xié)程,就add幾個(gè)
    // wg.Done() 我告知我已經(jīng)結(jié)束了  defer wg.Done()來(lái)在Goroutine完成時(shí)通知WaitGroup
    // 開啟幾個(gè)協(xié)程,就add幾個(gè)
    wg.Add(2)

    go test1()
    go test2()

    fmt.Println("main等待ing")
    wg.Wait() // 等待 wg 歸零,wg.Wait()來(lái)等待所有Goroutine完成 代碼才會(huì)繼續(xù)向下執(zhí)行
    fmt.Println("end")

    // 理想狀態(tài):所有協(xié)程執(zhí)行完畢之后,自動(dòng)停止。
    //如果每次都強(qiáng)制設(shè)置個(gè)等待時(shí)間。那么協(xié)程代碼也可能在這個(gè)時(shí)間內(nèi)還沒(méi)跑完,也可能提前就跑完了,所以設(shè)置死的等待時(shí)間不合理。此時(shí)就需要用到了等待組WaitGroup
    //time.Sleep(1 * time.Second)

}
func test1() {
    for i := 0; i < 10; i++ {
        time.Sleep(1 * time.Second)
        fmt.Println("test1--", i)
    }
    wg.Done() //這里就將該代碼塊放在了其他邏輯之后
}
func test2() {
    defer wg.Done() // defer wg.Done()來(lái)在Goroutine完成時(shí)通知WaitGroup  如果不用defer就得把該方法放在其他代碼之后
    for i := 0; i < 10; i++ {
        fmt.Println("test2--", i)
    }
}

主線程會(huì)等待所有協(xié)程執(zhí)行完畢,才繼續(xù)往下執(zhí)行代碼

在這里插入圖片描述

1.3 注意事項(xiàng)

避免死鎖:確保在獲取鎖之后,無(wú)論發(fā)生什么情況(包括panic),都能夠釋放鎖??梢允褂胐efer語(yǔ)句來(lái)確保鎖的釋放。

減少鎖的持有時(shí)間:鎖的持有時(shí)間越長(zhǎng),其他goroutine被阻塞的時(shí)間就越長(zhǎng),系統(tǒng)的并發(fā)性能就越差。因此,應(yīng)該盡量減少鎖的持有時(shí)間,只在必要的代碼段中持有鎖。

避免嵌套鎖:盡量避免在一個(gè)鎖已經(jīng)持有的情況下再嘗試獲取另一個(gè)鎖,這可能會(huì)導(dǎo)致死鎖。

避免忘記調(diào)用Done:如果忘記調(diào)用Done方法,WaitGroup將會(huì)永遠(yuǎn)等待下去,導(dǎo)致程序無(wú)法正常結(jié)束。

避免負(fù)數(shù)計(jì)數(shù)器:調(diào)用Add方法時(shí),如果傳入的參數(shù)為負(fù)數(shù),或者導(dǎo)致計(jì)數(shù)器變?yōu)樨?fù)數(shù),將會(huì)導(dǎo)致panic。

二、讀寫鎖(RWMutex)

讀寫鎖(sync.RWMutex)允許多個(gè)goroutine同時(shí)讀取資源,但在寫入時(shí)會(huì)阻塞所有其他讀和寫的goroutine。讀寫鎖可以提高讀多寫少的場(chǎng)景下的并發(fā)性能。

2.1 基本用法

package main

import (
    "fmt"
    "sync"
)

var (
    data map[string]int
    rwMu sync.RWMutex
)

func readData(key string) int {
    rwMu.RLock()
    defer rwMu.RUnlock()
    return data[key]
}

func writeData(key string, value int) {
    rwMu.Lock()
    defer rwMu.Unlock()
    data[key] = value
}

func main() {
    data = make(map[string]int)

    var wg sync.WaitGroup

    // 寫操作
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func(i int) {
            defer wg.Done()
            writeData(fmt.Sprintf("key%d", i), i*10)
        }(i)
    }

    // 讀操作
    for i := 0; i < 100; i++ {
        wg.Add(1)
        go func(i int) {
            defer wg.Done()
            value := readData(fmt.Sprintf("key%d", i%10))
            fmt.Printf("Read: key%d = %d\n", i%10, value)
        }(i)
    }

    wg.Wait()
}

在這里插入圖片描述

在上述代碼中,我們定義了一個(gè)全局變量data和一個(gè)讀寫鎖rwMu。readData函數(shù)用于讀取data中的值,在讀取之前先獲取讀鎖,讀取完成后釋放讀鎖。

writeData函數(shù)用于寫入data中的值,在寫入之前先獲取寫鎖,寫入完成后釋放寫鎖。

在main函數(shù)中,我們啟動(dòng)了10個(gè)寫goroutine和100個(gè)讀goroutine,分別調(diào)用writeData和readData函數(shù)。通過(guò)sync.WaitGroup等待所有g(shù)oroutine完成。

2.2 注意事項(xiàng)

避免寫鎖長(zhǎng)時(shí)間持有:寫鎖會(huì)阻塞所有其他讀和寫的goroutine,因此應(yīng)該盡量減少寫鎖的持有時(shí)間。
讀多寫少場(chǎng)景:讀寫鎖適用于讀多寫少的場(chǎng)景,如果寫操作非常頻繁,讀寫鎖的性能優(yōu)勢(shì)可能會(huì)消失。
避免嵌套鎖:與互斥鎖類似,讀寫鎖也應(yīng)該避免嵌套使用。

三、Once(一次執(zhí)行)

sync.Once用于確保某個(gè)操作只執(zhí)行一次,無(wú)論有多少個(gè)goroutine調(diào)用它。這對(duì)于單例模式或初始化只執(zhí)行一次的場(chǎng)景非常有用。

3.1 基本用法

package main

import (
    "fmt"
    "sync"
)

var (
    once    sync.Once
    message string
)

func initMessage() {
    message = "Hello, World!"
}

func printMessage() {
    once.Do(initMessage)
    fmt.Println(message)
}

func main() {
    var wg sync.WaitGroup

    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            printMessage()
        }()
    }

    wg.Wait()
}

在這里插入圖片描述

在上述代碼中,我們定義了一個(gè)全局變量message和一個(gè)sync.Once類型的變量once。

initMessage函數(shù)用于初始化message的值。printMessage函數(shù)通過(guò)once.Do方法確保initMessage只被調(diào)用一次,然后打印出message的值。

在main函數(shù)中,我們啟動(dòng)了10個(gè)goroutine,每個(gè)goroutine都調(diào)用printMessage函數(shù)。通過(guò)sync.WaitGroup等待所有g(shù)oroutine完成。

3.2 注意事項(xiàng)

避免重復(fù)初始化:sync.Once確保某個(gè)操作只執(zhí)行一次,因此它通常用于初始化全局變量或執(zhí)行其他只需要執(zhí)行一次的操作。

性能開銷:雖然sync.Once的性能開銷很小,但在高性能要求的場(chǎng)景下,仍然需要注意其使用。

四、總結(jié)

本文詳細(xì)介紹了Go語(yǔ)言中sync包中的鎖機(jī)制,包括互斥鎖(sync.Mutex)、讀寫鎖(sync.RWMutex)、Once(一次執(zhí)行)和WaitGroup(等待組)。

通過(guò)實(shí)際案例,幫助讀者理解和使用這些鎖。在并發(fā)編程中,正確地使用這些同步原語(yǔ),可以確保多個(gè)goroutine安全地訪問(wèn)共享資源,避免數(shù)據(jù)競(jìng)爭(zhēng)和其他并發(fā)問(wèn)題。希望本文能夠?qū)Υ蠹矣兴鶐椭?/p>

以上就是Go多線程中數(shù)據(jù)不一致問(wèn)題的解決方案的詳細(xì)內(nèi)容,更多關(guān)于Go多線程數(shù)據(jù)不一致的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Go語(yǔ)言如何處理HTTP身份驗(yàn)證教程示例

    Go語(yǔ)言如何處理HTTP身份驗(yàn)證教程示例

    這篇文章主要為大家介紹了Go語(yǔ)言如何處理HTTP身份驗(yàn)證教程示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2024-01-01
  • go-zero熔斷機(jī)制組件Breaker接口定義使用解析

    go-zero熔斷機(jī)制組件Breaker接口定義使用解析

    這篇文章主要為大家介紹了go-zero熔斷機(jī)制組件Breaker接口定義使用解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-05-05
  • Go中sync.Mutex 加鎖失效的問(wèn)題解決

    Go中sync.Mutex 加鎖失效的問(wèn)題解決

    sync.Mutex是Go標(biāo)準(zhǔn)庫(kù)中常用的一個(gè)排外鎖,本文主要介紹了Go中sync.Mutex 加鎖失效的問(wèn)題解決,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2024-08-08
  • Go語(yǔ)言fmt.Sprintf格式化輸出的語(yǔ)法與實(shí)例

    Go語(yǔ)言fmt.Sprintf格式化輸出的語(yǔ)法與實(shí)例

    Go 可以使用 fmt.Sprintf 來(lái)格式化字符串,下面這篇文章主要給大家介紹了關(guān)于Go語(yǔ)言fmt.Sprintf格式化輸出的語(yǔ)法與實(shí)例,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2022-07-07
  • goland使用go mod模式的步驟詳解

    goland使用go mod模式的步驟詳解

    這篇文章主要介紹了goland使用go mod模式的步驟詳解,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-12-12
  • 關(guān)于升級(jí)go1.18的goland問(wèn)題詳解

    關(guān)于升級(jí)go1.18的goland問(wèn)題詳解

    作為一個(gè)go語(yǔ)言程序員,覺得自己有義務(wù)為go新手開一條更簡(jiǎn)單便捷的上手之路,下面這篇文章主要給大家介紹了關(guān)于升級(jí)go1.18的goland問(wèn)題的相關(guān)資料,需要的朋友可以參考下
    2022-11-11
  • Go 語(yǔ)言json.Unmarshal 遇到的小問(wèn)題(推薦)

    Go 語(yǔ)言json.Unmarshal 遇到的小問(wèn)題(推薦)

    這篇文章主要介紹了 Go 語(yǔ)言json.Unmarshal 遇到的小問(wèn)題,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-07-07
  • Golang內(nèi)存泄漏詳解之原因、檢測(cè)與修復(fù)過(guò)程

    Golang內(nèi)存泄漏詳解之原因、檢測(cè)與修復(fù)過(guò)程

    本文詳細(xì)介紹了Golang中的內(nèi)存泄漏問(wèn)題,包括內(nèi)存泄漏的定義、分類、影響以及預(yù)防和修復(fù)方法,通過(guò)使用Golang自帶的性能分析工具和火焰圖工具,可以有效地檢測(cè)和定位內(nèi)存泄漏的代碼路徑,合理的代碼設(shè)計(jì)和定期的代碼審查也是預(yù)防內(nèi)存泄漏的關(guān)鍵
    2024-12-12
  • Go中Writer和Reader接口的使用入門

    Go中Writer和Reader接口的使用入門

    本文主要介紹了Go中Writer和Reader接口的使用入門,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-04-04
  • go?mode?tidy出現(xiàn)報(bào)錯(cuò)go:?warning:?“all“?matched?no?packages的解決方法

    go?mode?tidy出現(xiàn)報(bào)錯(cuò)go:?warning:?“all“?matched?no?package

    使用go的時(shí)候我們一般都會(huì)使用go?mode管理,下面這篇文章主要給大家介紹了關(guān)于go?mode?tidy出現(xiàn)報(bào)錯(cuò)go:?warning:?“all“?matched?no?packages的解決方法,文中通過(guò)圖文介紹的非常詳細(xì),需要的朋友可以參考下
    2022-08-08

最新評(píng)論