第五章,线程安全的map实现cmap这里,其实是不安全的。
sailucheng opened this issue · 1 comments
sailucheng commented
具体部分是bucket 实现这里。
假如不提供锁参数,单靠原子更新表头,会不会无法保证线程安全?
举个例子,bucket.delete
这里,您的实现是copy被删除元素的之前元素链,和之后元素,然后重新构建一条新的单向链表,原子更新firstValue,链表头。
问题就出在这里,假如Copy组成新链表的同一时刻,暂未更新链表头。这时候别的线程操作,更新,或者删除这个bucket链表段中的元素,那么当前copy的这段链表段就不是最新的数据了,然后用这条脏的链表段,更新表头,会覆盖了之前的操作,导致之前别的线程操作的丢失。
问题复现:
bucket.go
文件中,L135之前,添加time.Sleep(time.Second)
放大模拟一个删除延时。
bucket_test.go
func TestBucketDelaySecDeleteNoLocker(t *testing.T) {
b := newBucket()
wg := sync.WaitGroup{}
wg.Add(2)
p, _ := newPair("A", "a")
b.Put(p, nil)
p, _ = newPair("B", "b")
b.Put(p, nil)
p, _ = newPair("C", "c")
b.Put(p, nil)
//c b a
t.Logf("before bucket:\n%v\n", b)
go func() {
defer wg.Done()
p, _ := newPair("D", "d")
_, err := b.Put(p, nil)
if err != nil {
t.Fatalf("An error occurs when putting a pair to the bucket: %s (pair: %#v)", b, p)
}
// bucket should be D C B A
t.Logf("after put item D, bucket:\n%v\n", b)
}()
go func() {
defer wg.Done()
// delete item C
b.Delete("C", nil)
}()
wg.Wait()
expected := []string{"D", "B", "A"}
actual := make([]string, 0)
for curr := b.GetFirstPair(); curr != nil; curr = curr.Next() {
actual = append(actual, curr.Key())
}
t.Logf("after delete C , bucket:\n%v\n", b)
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("expected %v , but %v", expected, actual)
}
}
=== RUN TestBucketDelaySecDeleteNoLocker
bucket_test.go:323: before bucket:
[ pair{key:C, hash:67, element:c, nextKey:B} pair{key:B, hash:66, element:b, nextKey:A} pair{key:A, hash:65, element:a, nextKey:} ]
bucket_test.go:333: after put item D, bucket:
[ pair{key:D, hash:68, element:d, nextKey:C} pair{key:C, hash:67, element:c, nextKey:B} pair{key:B, hash:66, element:b, nextKey:A} pair{key:A, hash:65, element:a, nextKey:} ]
bucket_test.go:349: after delete C , bucket:
[ pair{key:B, hash:66, element:b, nextKey:A} pair{key:A, hash:65, element:a, nextKey:} ]
bucket_test.go:352: expected [D B A] , but [B A]
--- FAIL: TestBucketDelaySecDeleteNoLocker (1.01s)
FAIL
hyper0x commented
这种操作在segment层面是有锁保护的。你在进行b.Put(p, nil)
和 b.Delete("C", nil)
操作时把第二个参数置为 nil
就可能会导致并发安全问题。Bucket实际上是把“是否绝对并发安全”的选择权抛给了上层(这里是Segment)。