簡體   English   中英

甚至使用互斥體進行自定義並發映射的Golang數據競賽

[英]Golang data race even with mutex for custom concurrent maps

這是我為學習目的編寫的簡單並發映射

    package concurrent_hashmap

    import (
        "hash/fnv"
        "sync"
    )

    type ConcurrentMap struct {
        buckets []ThreadSafeMap
        bucketCount uint32
    }

    type ThreadSafeMap struct {
        mapLock sync.RWMutex
        hashMap map[string]interface{}
    }

    func NewConcurrentMap(bucketSize uint32) *ConcurrentMap {
        var threadSafeMapInstance ThreadSafeMap
        var bucketOfThreadSafeMap []ThreadSafeMap

        for i := 0; i <= int(bucketSize); i++ {
            threadSafeMapInstance = ThreadSafeMap{sync.RWMutex{}, make(map[string]interface{})}
            bucketOfThreadSafeMap = append(bucketOfThreadSafeMap, threadSafeMapInstance)
        }

        return &ConcurrentMap{bucketOfThreadSafeMap, bucketSize}
    }

    func (cMap *ConcurrentMap) Put(key string, val interface{}) {
        bucketIndex := hash(key) % cMap.bucketCount
        bucket := cMap.buckets[bucketIndex]
        bucket.mapLock.Lock()
        bucket.hashMap[key] = val
        bucket.mapLock.Unlock()
    }

    // Helper
    func hash(s string) uint32 {
        h := fnv.New32a()
        h.Write([]byte(s))
        return h.Sum32()
    }

我正在嘗試編寫一個簡單的基准,我發現同步訪問將正確運行,但並發訪問將獲得

fatal error: concurrent map writes

這是我使用go test -bench=. -race基准go test -bench=. -race go test -bench=. -race

package concurrent_hashmap

import (
    "testing"
    "runtime"
    "math/rand"
    "strconv"
    "sync"
)
// Concurrent does not work
func BenchmarkMyFunc(b *testing.B) {
    var wg sync.WaitGroup

    runtime.GOMAXPROCS(runtime.NumCPU())

    my_map := NewConcurrentMap(uint32(4))
    for n := 0; n < b.N; n++ {
        go insert(my_map, wg)
    }
    wg.Wait()
}

func insert(my_map *ConcurrentMap, wg sync.WaitGroup) {
    wg.Add(1)
    var rand_int int
    for element_num := 0; element_num < 1000; element_num++ {
        rand_int = rand.Intn(100)
        my_map.Put(strconv.Itoa(rand_int), rand_int)
    }
    defer wg.Done()
}

// This works
func BenchmarkMyFuncSynchronize(b *testing.B) {
    my_map := NewConcurrentMap(uint32(4))
    for n := 0; n < b.N; n++ {
        my_map.Put(strconv.Itoa(123), 123)
    }
}

WARNING: DATA RACE bucket.hashMap[key] = val是在說bucket.hashMap[key] = val引起了問題,但是我對為什么可能這樣感到困惑,因為無論何時寫操作都會鎖定該邏輯。

我認為我缺少一些基本知識,有人可以指出我的錯誤嗎?

謝謝

EDIT1:

不知道這是否有幫助,但是如果我不鎖定任何東西,這就是我的互斥鎖的樣子

{{0 0} 0 0 0 0}

這是我鎖定寫入后的樣子

{{1 0} 0 0 -1073741824 0}

不知道為什么我的readerCount是一個低負數

編輯:2

我想我找到了問題所在,但是不確定為什么我必須這樣編碼

問題是

type ThreadSafeMap struct {
    mapLock sync.RWMutex // This is causing problem
    hashMap map[string]interface{}
}

它應該是

type ThreadSafeMap struct {
    mapLock *sync.RWMutex
    hashMap map[string]interface{}
}

另一個奇怪的事情是在Put如果我將print語句放在鎖中

bucket.mapLock.Lock()
fmt.Println("start")
fmt.Println(bucket)
fmt.Println(bucketIndex)
fmt.Println(bucket.mapLock)
fmt.Println(&bucket.mapLock)
bucket.hashMap[key] = val
defer bucket.mapLock.Unlock()

可以進行以下打印

start
start
{0x4212861c0 map[123:123]}
{0x4212241c0 map[123:123]}

這很奇怪,因為每個start打印輸出后都應跟隨4行存儲桶信息,因為您無法連續背對背start ,因為這將表明多個線程正在訪問鎖內的行

同樣由於某種原因,即使我將bucketIndex設為靜態,每個bucket.mapLock都有不同的地址,這表明我什至沒有訪問相同的鎖。

但是盡管有上述怪異,但將互斥體更改為指針仍解決了我的問題

我很想找出為什么我需要互斥鎖的指針,以及為什么打印結果似乎表明多個線程正在訪問該鎖以及每個鎖具有不同的地址。

問題在於語句

bucket := cMap.buckets[bucketIndex]

bucket現在在該索引處包含ThreadSafeMap副本。 由於sync.RWMutex作為值存儲,因此在分配時將其復制。 但是,映射映射保留對基礎數據結構的引用,因此將傳遞指針或同一映射的副本。 該代碼在寫入單個映射時會鎖定該鎖的副本,這會導致問題。

因此,將sync.RWMutex更改為*sync.RWMutex時,您不會遇到任何問題。 如圖所示,最好將對結構的引用存儲在地圖中。

package concurrent_hashmap

import (
    "hash/fnv"
    "sync"
)

type ConcurrentMap struct {
    buckets     []*ThreadSafeMap
    bucketCount uint32
}

type ThreadSafeMap struct {
    mapLock sync.RWMutex
    hashMap map[string]interface{}
}

func NewConcurrentMap(bucketSize uint32) *ConcurrentMap {
    var threadSafeMapInstance *ThreadSafeMap
    var bucketOfThreadSafeMap []*ThreadSafeMap

    for i := 0; i <= int(bucketSize); i++ {
        threadSafeMapInstance = &ThreadSafeMap{sync.RWMutex{}, make(map[string]interface{})}
        bucketOfThreadSafeMap = append(bucketOfThreadSafeMap, threadSafeMapInstance)
    }

    return &ConcurrentMap{bucketOfThreadSafeMap, bucketSize}
}

func (cMap *ConcurrentMap) Put(key string, val interface{}) {
    bucketIndex := hash(key) % cMap.bucketCount
    bucket := cMap.buckets[bucketIndex]
    bucket.mapLock.Lock()
    bucket.hashMap[key] = val
    bucket.mapLock.Unlock()
}

// Helper
func hash(s string) uint32 {
    h := fnv.New32a()
    h.Write([]byte(s))
    return h.Sum32()
}

可以通過如下修改Put函數來驗證場景

func (cMap *ConcurrentMap) Put(key string, val interface{}) {
    //fmt.Println("index", key)
    bucketIndex := 1
    bucket := cMap.buckets[bucketIndex]
    fmt.Printf("%p %p\n", &(bucket.mapLock), bucket.hashMap)
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM