偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

Go 語(yǔ)言 Map 是并發(fā)安全的嗎?

開(kāi)發(fā) 前端
在多個(gè) goroutine 同時(shí)訪(fǎng)問(wèn)同一個(gè) map 時(shí),可能會(huì)出現(xiàn)并發(fā)不安全的現(xiàn)象。這是因?yàn)?Go 語(yǔ)言中的 map 并沒(méi)有內(nèi)置鎖來(lái)保護(hù)對(duì)map的訪(fǎng)問(wèn)。

Go 語(yǔ)言中的 map 是一個(gè)非常常用的數(shù)據(jù)結(jié)構(gòu),它允許我們快速地存儲(chǔ)和檢索鍵值對(duì)。然而,在并發(fā)場(chǎng)景下使用 map 時(shí),還是有一些問(wèn)題需要注意的。

本文將探討 Go 語(yǔ)言中的 map 是否是并發(fā)安全的,并提供三種方案來(lái)解決并發(fā)問(wèn)題。

先來(lái)回答一下題目的問(wèn)題,答案就是并發(fā)不安全。

看一段代碼示例,當(dāng)兩個(gè) goroutine 同時(shí)對(duì)同一個(gè) map 進(jìn)行寫(xiě)操作時(shí),會(huì)發(fā)生什么?

package main

import "sync"

func main() {
    m := make(map[string]int)
    m["foo"] = 1

    var wg sync.WaitGroup
    wg.Add(2)

    go func() {
        for i := 0; i < 1000; i++ {
            m["foo"]++
        }
        wg.Done()
    }()

    go func() {
        for i := 0; i < 1000; i++ {
            m["foo"]++
        }
        wg.Done()
    }()

    wg.Wait()
}

在這個(gè)例子中,我們可以看到,兩個(gè) goroutine 將嘗試同時(shí)對(duì) map 進(jìn)行寫(xiě)入。運(yùn)行這個(gè)程序時(shí),我們將看到一個(gè)錯(cuò)誤:

fatal error: concurrent map writes

也就是說(shuō),在并發(fā)場(chǎng)景下,這樣操作 map 是不行的。

為什么是不安全的

因?yàn)樗鼪](méi)有內(nèi)置的鎖機(jī)制來(lái)保護(hù)多個(gè) goroutine 同時(shí)對(duì)其進(jìn)行讀寫(xiě)操作。

當(dāng)多個(gè) goroutine 同時(shí)對(duì)同一個(gè) map 進(jìn)行讀寫(xiě)操作時(shí),就會(huì)出現(xiàn)數(shù)據(jù)競(jìng)爭(zhēng)和不一致的結(jié)果。

就像上例那樣,當(dāng)兩個(gè) goroutine 同時(shí)嘗試更新同一個(gè)鍵值對(duì)時(shí),最終的結(jié)果可能取決于哪個(gè) goroutine 先完成了更新操作。這種不確定性可能會(huì)導(dǎo)致程序出現(xiàn)錯(cuò)誤或崩潰。

Go 語(yǔ)言團(tuán)隊(duì)沒(méi)有將 map 設(shè)計(jì)成并發(fā)安全的,是因?yàn)檫@樣會(huì)增加程序的開(kāi)銷(xiāo)并降低性能。

如果 map 內(nèi)置了鎖機(jī)制,那么每次訪(fǎng)問(wèn) map 時(shí)都需要進(jìn)行加鎖和解鎖操作,這會(huì)增加程序的運(yùn)行時(shí)間并降低性能。

此外,并不是所有的程序都需要在并發(fā)場(chǎng)景下使用 map,因此將鎖機(jī)制內(nèi)置到 map 中會(huì)對(duì)那些不需要并發(fā)安全的程序造成不必要的開(kāi)銷(xiāo)。

在實(shí)際使用過(guò)程中,開(kāi)發(fā)人員可以根據(jù)程序的需求來(lái)選擇是否需要保證 map 的并發(fā)安全性,從而在性能和安全性之間做出權(quán)衡。

如何并發(fā)安全

接下來(lái)介紹三種并發(fā)安全的方式:

  1. 讀寫(xiě)鎖
  2. 分片加鎖
  3. sync.Map

加讀寫(xiě)鎖

第一種方法是使用讀寫(xiě)鎖,這是最容易想到的一種方式。在讀操作時(shí)加讀鎖,在寫(xiě)操作時(shí)加寫(xiě)鎖。

package main

import (
    "fmt"
    "sync"
)

type SafeMap struct {
    sync.RWMutex
    Map map[string]string
}

func NewSafeMap() *SafeMap {
    sm := new(SafeMap)
    sm.Map = make(map[string]string)
    return sm
}

func (sm *SafeMap) ReadMap(key string) string {
    sm.RLock()
    value := sm.Map[key]
    sm.RUnlock()
    return value
}

func (sm *SafeMap) WriteMap(key string, value string) {
    sm.Lock()
    sm.Map[key] = value
    sm.Unlock()
}

func main() {
    safeMap := NewSafeMap()

    var wg sync.WaitGroup

    // 啟動(dòng)多個(gè)goroutine進(jìn)行寫(xiě)操作
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func(i int) {
            defer wg.Done()
            safeMap.WriteMap(fmt.Sprintf("name%d", i), fmt.Sprintf("John%d", i))
        }(i)
    }

    wg.Wait()

    // 啟動(dòng)多個(gè)goroutine進(jìn)行讀操作
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func(i int) {
            defer wg.Done()
            fmt.Println(safeMap.ReadMap(fmt.Sprintf("name%d", i)))
        }(i)
    }

    wg.Wait()
}

在這個(gè)示例中,我們定義了一個(gè) SafeMap 結(jié)構(gòu)體,它包含一個(gè) sync.RWMutex 和一個(gè) map[string]string。

定義了兩個(gè)方法:ReadMap 和 WriteMap。在 ReadMap 方法中,我們使用讀鎖來(lái)保護(hù)對(duì) map 的讀取操作。在 WriteMap 方法中,我們使用寫(xiě)鎖來(lái)保護(hù)對(duì) map 的寫(xiě)入操作。

在 main 函數(shù)中,我們啟動(dòng)了多個(gè) goroutine 來(lái)進(jìn)行讀寫(xiě)操作,這些操作都是安全的。

分片加鎖

上例中通過(guò)對(duì)整個(gè) map 加鎖來(lái)實(shí)現(xiàn)需求,但相對(duì)來(lái)說(shuō),鎖會(huì)大大降低程序的性能,那如何優(yōu)化呢?其中一個(gè)優(yōu)化思路就是降低鎖的粒度,不對(duì)整個(gè) map 進(jìn)行加鎖。

這種方法是分片加鎖,將這個(gè) map 分成 n 塊,每個(gè)塊之間的讀寫(xiě)操作都互不干擾,從而降低沖突的可能性。

package main

import (
    "fmt"
    "sync"
)

const N = 16

type SafeMap struct {
    maps  [N]map[string]string
    locks [N]sync.RWMutex
}

func NewSafeMap() *SafeMap {
    sm := new(SafeMap)
    for i := 0; i < N; i++ {
        sm.maps[i] = make(map[string]string)
    }
    return sm
}

func (sm *SafeMap) ReadMap(key string) string {
    index := hash(key) % N
    sm.locks[index].RLock()
    value := sm.maps[index][key]
    sm.locks[index].RUnlock()
    return value
}

func (sm *SafeMap) WriteMap(key string, value string) {
    index := hash(key) % N
    sm.locks[index].Lock()
    sm.maps[index][key] = value
    sm.locks[index].Unlock()
}

func hash(s string) int {
    h := 0
    for i := 0; i < len(s); i++ {
        h = 31*h + int(s[i])
    }
    return h
}

func main() {
    safeMap := NewSafeMap()

    var wg sync.WaitGroup

    // 啟動(dòng)多個(gè)goroutine進(jìn)行寫(xiě)操作
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func(i int) {
            defer wg.Done()
            safeMap.WriteMap(fmt.Sprintf("name%d", i), fmt.Sprintf("John%d", i))
        }(i)
    }

    wg.Wait()

    // 啟動(dòng)多個(gè)goroutine進(jìn)行讀操作
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func(i int) {
            defer wg.Done()
            fmt.Println(safeMap.ReadMap(fmt.Sprintf("name%d", i)))
        }(i)
    }

    wg.Wait()
}

在這個(gè)示例中,我們定義了一個(gè) SafeMap 結(jié)構(gòu)體,它包含一個(gè)長(zhǎng)度為 N 的 map 數(shù)組和一個(gè)長(zhǎng)度為 N 的鎖數(shù)組。

定義了兩個(gè)方法:ReadMap 和 WriteMap。在這兩個(gè)方法中,我們都使用了一個(gè) hash 函數(shù)來(lái)計(jì)算 key 應(yīng)該存儲(chǔ)在哪個(gè) map 中。然后再對(duì)這個(gè) map 進(jìn)行讀寫(xiě)操作。

在 main 函數(shù)中,我們啟動(dòng)了多個(gè) goroutine 來(lái)進(jìn)行讀寫(xiě)操作,這些操作都是安全的。

有一個(gè)開(kāi)源項(xiàng)目 orcaman/concurrent-map 就是通過(guò)這種思想來(lái)做的,感興趣的同學(xué)可以看看。

sync.Map

最后,在內(nèi)置的 sync 包中(Go 1.9+)也有一個(gè)線(xiàn)程安全的 map,通過(guò)將讀寫(xiě)分離的方式實(shí)現(xiàn)了某些特定場(chǎng)景下的性能提升。

package main

import (
    "fmt"
    "sync"
)

func main() {
    var m sync.Map
    var wg sync.WaitGroup

    // 啟動(dòng)多個(gè)goroutine進(jìn)行寫(xiě)操作
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func(i int) {
            defer wg.Done()
            m.Store(fmt.Sprintf("name%d", i), fmt.Sprintf("John%d", i))
        }(i)
    }

    wg.Wait()

    // 啟動(dòng)多個(gè)goroutine進(jìn)行讀操作
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func(i int) {
            defer wg.Done()
            v, _ := m.Load(fmt.Sprintf("name%d", i))
            fmt.Println(v.(string))
        }(i)
    }

    wg.Wait()
}

有了官方的支持,代碼瞬間少了很多,使用起來(lái)方便多了。

在這個(gè)示例中,我們使用了內(nèi)置的 sync.Map 類(lèi)型來(lái)存儲(chǔ)鍵值對(duì),使用 Store 方法來(lái)存儲(chǔ)鍵值對(duì),使用 Load 方法來(lái)獲取鍵值對(duì)。

在 main 函數(shù)中,我們啟動(dòng)了多個(gè) goroutine 來(lái)進(jìn)行讀寫(xiě)操作,這些操作都是安全的。

總結(jié)

Go 語(yǔ)言中的 map 本身并不是并發(fā)安全的。

在多個(gè) goroutine 同時(shí)訪(fǎng)問(wèn)同一個(gè) map 時(shí),可能會(huì)出現(xiàn)并發(fā)不安全的現(xiàn)象。這是因?yàn)?Go 語(yǔ)言中的 map 并沒(méi)有內(nèi)置鎖來(lái)保護(hù)對(duì)map的訪(fǎng)問(wèn)。

盡管如此,我們?nèi)匀豢梢允褂靡恍┓椒▉?lái)實(shí)現(xiàn) map 的并發(fā)安全。

一種方法是使用讀寫(xiě)鎖,在讀操作時(shí)加讀鎖,在寫(xiě)操作時(shí)加寫(xiě)鎖。

另一種方法是分片加鎖,將這個(gè) map 分成 n 塊,每個(gè)塊之間的讀寫(xiě)操作都互不干擾,從而降低沖突的可能性。

此外,在內(nèi)置的 sync 包中(Go 1.9+)也有一個(gè)線(xiàn)程安全的 map,它通過(guò)將讀寫(xiě)分離的方式實(shí)現(xiàn)了某些特定場(chǎng)景下的性能提升。

以上就是本文的全部?jī)?nèi)容,如果覺(jué)得還不錯(cuò)的話(huà)歡迎點(diǎn)贊,轉(zhuǎn)發(fā)和關(guān)注,感謝支持。

參考文章:

  • https://zhuanlan.zhihu.com/p/356739568

責(zé)任編輯:武曉燕 來(lái)源: AlwaysBeta
相關(guān)推薦

2024-04-07 00:04:00

Go語(yǔ)言Map

2022-11-22 08:01:30

2022-04-06 08:19:13

Go語(yǔ)言切片

2022-01-10 23:54:56

GoMap并發(fā)

2021-06-08 11:15:10

Redis數(shù)據(jù)庫(kù)命令

2022-03-04 10:07:45

Go語(yǔ)言字節(jié)池

2024-01-01 08:10:40

Go語(yǔ)言map

2024-01-05 08:45:35

Go語(yǔ)言map

2023-12-21 07:09:32

Go語(yǔ)言任務(wù)

2021-07-30 07:28:15

WorkerPoolGo語(yǔ)言

2013-05-28 09:43:38

GoGo語(yǔ)言并發(fā)模式

2021-07-15 23:18:48

Go語(yǔ)言并發(fā)

2023-11-30 08:09:02

Go語(yǔ)言

2023-07-28 08:04:56

StringHeaatomic線(xiàn)程

2023-05-19 08:01:57

Go 語(yǔ)言map

2024-12-31 11:40:05

2023-02-10 09:40:36

Go語(yǔ)言并發(fā)

2012-06-15 09:56:40

2023-11-21 15:46:13

Go內(nèi)存泄漏

2019-05-15 11:38:22

GoogleGo編程語(yǔ)言
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)