这是我为学习目的而编写的简单并发地图
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
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
导致问题,但我很困惑为什么这是可能的,因为我会在写入时锁定逻辑。
我认为我遗漏了一些基本的东西,有人可以指出我的错误吗?
由于
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
也有不同的地址,这表明我甚至没有访问同一个锁。
但是尽管上面的奇怪之处在于将互斥体改为指针解决了我的问题
我很想知道为什么我需要指向互斥锁的指针以及为什么打印出来似乎表明多个线程正在访问锁定以及为什么每个锁具有不同的地址。
答案 0 :(得分:0)
问题在于声明
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)
}