我正在尝试将一般地图(使用interface{}
作为键和值)包装为我命名为MemStore
的内存中键值存储。但它不是线程安全的,尽管我使用sync.RWMutex
来锁定对底层地图的访问。我确实从单个goroutine使用时验证它工作正常。但是,只有两个并发goroutine访问它会产生panic: runtime error: invalid memory address or nil pointer dereference
。
导致此问题的原因是什么,在Go中实现线程安全的正确方法是什么?虽然在这个例子中,单个goroutine与地图交互的通道可以工作,但我特别寻找一种可以使用显式锁定的解决方案。 文件keyval.go:
package keyval
import "sync"
type MemStore struct {
data map[interface{}]interface{}
mutex sync.RWMutex
}
func NewMemStore() (MemStore) {
m := MemStore{
data: make(map[interface{}]interface{}),
// mutex does not need initializing
}
return m
}
func (m MemStore) Set(key interface{}, value interface{}) (err error) {
m.mutex.Lock()
defer m.mutex.Unlock()
if value != nil {
m.data[key] = value
} else {
delete(m.data, key);
}
return nil
}
文件keyval_test.go:
package keyval
import "testing"
func setN(store Store, N int, done chan<- struct{}) {
for i := 0; i < N; i++ {
store.Set(i, -i)
}
done <- struct{}{}
}
func BenchmarkMemStore(b *testing.B) {
store := NewMemStore()
done := make(chan struct{})
b.ResetTimer()
go setN(store, b.N, done)
go setN(store, b.N, done)
<-done
<-done
}
go test -bench .
的输出:
testing: warning: no tests to run
PASS
BenchmarkMemStore panic: runtime error: invalid memory address or nil pointer dereference
[signal 0xb code=0x1 addr=0x1 pc=0x80502eb]
goroutine 8 [running]:
runtime.panic(0x810f180, 0x821fc88)
/usr/lib/go/src/pkg/runtime/panic.c:266 +0xac
github.com/pyramids/keyval.MemStore.Set(0x1852e4a0, 0x0, 0x0, 0x0, 0x0, ...)
/home/steinb/go/src/github.com/pyramids/keyval/keyval.go:200 +0xb6
github.com/pyramids/keyval.(*MemStore).Set(0x1852efe0, 0x80f38c0, 0x19ff, 0x80f38c0, 0xffffe601, ...)
/home/steinb/go/src/github.com/pyramids/keyval/keyval.go:1 +0xa1
github.com/pyramids/keyval.setN(0xb77b89d8, 0x1852efe0, 0x2710, 0x1851c090)
/home/steinb/go/src/github.com/pyramids/keyval/memstore_test.go:69 +0x5b
created by github.com/pyramids/keyval.BenchmarkMemStore
/home/steinb/go/src/github.com/pyramids/keyval/memstore_test.go:89 +0x116
goroutine 1 [chan receive]:
testing.(*B).run(0x1854a000, 0x3, 0xb76cdeb4, 0x1, 0x1, ...)
/usr/lib/go/src/pkg/testing/benchmark.go:171 +0x4b
testing.RunBenchmarks(0x814d840, 0x821c828, 0x1, 0x1)
/usr/lib/go/src/pkg/testing/benchmark.go:303 +0x464
testing.Main(0x814d840, 0x8222220, 0x0, 0x0, 0x821c828, ...)
/usr/lib/go/src/pkg/testing/testing.go:411 +0x151
main.main()
github.com/pyramids/keyval/_test/_testmain.go:47 +0x83
goroutine 3 [chan receive]:
github.com/pyramids/keyval.BenchmarkMemStore(0x1854a000)
/home/steinb/go/src/github.com/pyramids/keyval/memstore_test.go:91 +0x188
testing.(*B).runN(0x1854a000, 0x2710)
/usr/lib/go/src/pkg/testing/benchmark.go:119 +0x7a
testing.(*B).launch(0x1854a000)
/usr/lib/go/src/pkg/testing/benchmark.go:207 +0x12c
created by testing.(*B).run
/usr/lib/go/src/pkg/testing/benchmark.go:170 +0x32
goroutine 9 [runnable]:
sync.(*RWMutex).Lock(0x18582a64)
/usr/lib/go/src/pkg/sync/rwmutex.go:72
github.com/pyramids/keyval.MemStore.Set(0x1852e4a0, 0x0, 0x0, 0x0, 0x0, ...)
/home/steinb/go/src/github.com/pyramids/keyval/keyval.go:179 +0x5d
github.com/pyramids/keyval.(*MemStore).Set(0x1852efc0, 0x80f38c0, 0x2302, 0x80f38c0, 0xffffdcfe, ...)
/home/steinb/go/src/github.com/pyramids/keyval/keyval.go:1 +0xa1
github.com/pyramids/keyval.setN(0xb77b89d8, 0x1852efc0, 0x2710, 0x1851c090)
/home/steinb/go/src/github.com/pyramids/keyval/memstore_test.go:69 +0x5b
created by github.com/pyramids/keyval.BenchmarkMemStore
/home/steinb/go/src/github.com/pyramids/keyval/memstore_test.go:90 +0x172
exit status 2
FAIL github.com/pyramids/keyval 0.056s
答案 0 :(得分:2)
关于接收器的指针与值的规则是值方法 可以在指针和值上调用,但指针方法只能是 在指针上调用。这是因为指针方法可以修改 接收器;在值的副本上调用它们会导致这些 修改被丢弃。
要明显修改MemStore
struct
变量mutex
字段,请使用指针接收器。您正在修改副本,这对其他go例程是不可见的。例如,
档案keyval.go
:
package keyval
import "sync"
type MemStore struct {
data map[interface{}]interface{}
mutex sync.RWMutex
}
func NewMemStore() *MemStore {
m := &MemStore{
data: make(map[interface{}]interface{}),
// mutex does not need initializing
}
return m
}
func (m *MemStore) Set(key interface{}, value interface{}) (err error) {
m.mutex.Lock()
defer m.mutex.Unlock()
if value != nil {
m.data[key] = value
} else {
delete(m.data, key)
}
return nil
}
档案keyval_test.go
:
package keyval
import "testing"
func setN(store *MemStore, N int, done chan<- struct{}) {
for i := 0; i < N; i++ {
store.Set(i, -i)
}
done <- struct{}{}
}
func BenchmarkMemStore(b *testing.B) {
store := NewMemStore()
done := make(chan struct{})
b.ResetTimer()
go setN(store, b.N, done)
go setN(store, b.N, done)
<-done
<-done
}
基准:
$ go test -v -bench=.
testing: warning: no tests to run
PASS
BenchmarkMemStore 1000000 1244 ns/op
ok so/test 1.275s
$