简体   繁体   English

甚至使用互斥体进行自定义并发映射的Golang数据竞赛

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

Here is a simple concurrent map that I wrote for learning purpose 这是我为学习目的编写的简单并发映射

    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()
    }

I am trying to write a simple benchmark and I find that synchronize access will work correctly but concurrent access will get 我正在尝试编写一个简单的基准,我发现同步访问将正确运行,但并发访问将获得

fatal error: concurrent map writes

Here is my benchmark run with go test -bench=. -race 这是我使用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)
    }
}

The WARNING: DATA RACE is saying that bucket.hashMap[key] = val is causing the problem, but I am confused on why that is possible, since I lock that logic whenever write is happening. WARNING: DATA RACE bucket.hashMap[key] = val是在说bucket.hashMap[key] = val引起了问题,但是我对为什么可能这样感到困惑,因为无论何时写操作都会锁定该逻辑。

I think I am missing something basic, can someone point out my mistake? 我认为我缺少一些基本知识,有人可以指出我的错误吗?

Thanks 谢谢

Edit1: EDIT1:

Not sure if this helps but here is what my mutex looks like if I don't lock anything 不知道这是否有帮助,但是如果我不锁定任何东西,这就是我的互斥锁的样子

{{0 0} 0 0 0 0}

Here is what it looks like if I lock the write 这是我锁定写入后的样子

{{1 0} 0 0 -1073741824 0}

Not sure why my readerCount is a low negative number 不知道为什么我的readerCount是一个低负数

Edit:2 编辑:2

I think I find where the issue is at, but not sure why I have to code that way 我想我找到了问题所在,但是不确定为什么我必须这样编码

The issue is 问题是

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

it should be 它应该是

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

Another weird thing is that in Put if I put print statement inside lock 另一个奇怪的事情是在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()

The following prints is possible 可以进行以下打印

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

Its weird because each start printout should be follow with 4 lines of bucket info since you cannot have start back to back because that would indicate that multiple thread is access the line inside lock 这很奇怪,因为每个start打印输出后都应跟随4行存储桶信息,因为您无法连续背对背start ,因为这将表明多个线程正在访问锁内的行

Also for some reason each bucket.mapLock have different address even if I make the bucketIndex static, that indicate that I am not even accessing the same lock. 同样由于某种原因,即使我将bucketIndex设为静态,每个bucket.mapLock都有不同的地址,这表明我什至没有访问相同的锁。

But despite the above weirdness changing mutex to pointer solves my problem 但是尽管有上述怪异,但将互斥体更改为指针仍解决了我的问题

I would love to find out why I need pointers for mutex and why the prints seem to indicate multiple thread is accessing the lock and why each lock has different address. 我很想找出为什么我需要互斥锁的指针,以及为什么打印结果似乎表明多个线程正在访问该锁以及每个锁具有不同的地址。

The problem is with the statement 问题在于语句

bucket := cMap.buckets[bucketIndex]

bucket now contains copy of the ThreadSafeMap at that index. bucket现在在该索引处包含ThreadSafeMap副本。 As sync.RWMutex is stored as value, a copy of it is made while assigning. 由于sync.RWMutex作为值存储,因此在分配时将其复制。 But map maps hold references to an underlying data structure, so the copy of the pointer or the same map is passed. 但是,映射映射保留对基础数据结构的引用,因此将传递指针或同一映射的副本。 The code locks a copy of the lock while writing to a single map, which cause the problem. 该代码在写入单个映射时会锁定该锁的副本,这会导致问题。

Thats why you don't face any problem when you change sync.RWMutex to *sync.RWMutex . 因此,将sync.RWMutex更改为*sync.RWMutex时,您不会遇到任何问题。 It's better to store reference to structure in map as shown. 如图所示,最好将对结构的引用存储在地图中。

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()
}

It's possible to validate the scenario by modifying the function Put as follows 可以通过如下修改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