gopcp/example.v2

第五章,线程安全的map实现cmap这里,其实是不安全的。

sailucheng opened this issue · 1 comments

具体部分是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

这种操作在segment层面是有锁保护的。你在进行b.Put(p, nil)b.Delete("C", nil) 操作时把第二个参数置为 nil 就可能会导致并发安全问题。Bucket实际上是把“是否绝对并发安全”的选择权抛给了上层(这里是Segment)。