Golang数据竞争,即使是自定义并发映射的互斥锁

Golang数据竞争,即使是自定义并发映射的互斥锁,go,concurrency,benchmarking,goroutine,Go,Concurrency,Benchmarking,Goroutine,这是一个简单的并发映射,我是为了学习而写的 package concurrent_hashmap import ( "hash/fnv" "sync" ) type ConcurrentMap struct { buckets []ThreadSafeMap bucketCount uint32 } type ThreadSafeMap struct { mapL

这是一个简单的并发映射,我是为了学习而写的

    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()
    }
下面是我使用
go test-bench=运行的基准测试-种族

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)
    }
}
这是我锁上写的东西的样子

{{1 0} 0 0 -1073741824 0}
不确定为什么my是一个低负数

编辑: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行bucket信息,因为您不能让
start
背靠背,因为这表明多个线程正在访问锁内的行

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()
此外,出于某种原因,即使我将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()
}

我不知道竞争是什么,但作为旁白,你真的应该使用
defer
来解锁你的互斥锁(以防出现恐慌)。也不是竞争,但
我认为你链接的文件第88行解释了为什么
readerCount
是一个低负数。@smarx oh readerCount现在有意义了,谢谢你指出这一点,感谢您指出我之前在您的代码上运行的边界逻辑错误。您正在复制互斥体和waitgroup,并且在goroutine已被调度之后无法添加到等待组。
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()
}
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)
}