diff --git a/.CHANGELOG.md b/.CHANGELOG.md index 905ad1b..6d0966d 100644 --- a/.CHANGELOG.md +++ b/.CHANGELOG.md @@ -1,5 +1,5 @@ # 开发中 - +- [syncx: 支持分key加锁](https://github.com/ecodeclub/ekit/pull/224) # v0.0.8 - [atomicx: 泛型封装 atomic.Value](https://github.com/gotomicro/ekit/pull/101) - [queue: API 定义](https://github.com/gotomicro/ekit/pull/109) diff --git a/syncx/segment_key_lock.go b/syncx/segment_key_lock.go new file mode 100644 index 0000000..329dd33 --- /dev/null +++ b/syncx/segment_key_lock.go @@ -0,0 +1,71 @@ +// Copyright 2021 ecodeclub +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package syncx + +import ( + "hash/fnv" + "sync" +) + +// SegmentKeysLock 部分key lock结构定义 +type SegmentKeysLock struct { + locks []*sync.RWMutex +} + +// NewSegmentKeysLock 创建 SegmentKeysLock 示例 +func NewSegmentKeysLock(size int) *SegmentKeysLock { + locks := make([]*sync.RWMutex, size) + for i := range locks { + locks[i] = &sync.RWMutex{} + } + return &SegmentKeysLock{ + locks: locks, + } +} + +// hash 索引锁的hash函数 +func (s *SegmentKeysLock) hash(key string) uint32 { + h := fnv.New32a() + h.Write([]byte(key)) + return h.Sum32() +} + +// RLock 读锁加锁 +func (s *SegmentKeysLock) RLock(key string) { + hash := s.hash(key) + lock := s.locks[hash%uint32(len(s.locks))] + lock.RLock() +} + +// RUnlock 读锁解锁 +func (s *SegmentKeysLock) RUnlock(key string) { + hash := s.hash(key) + lock := s.locks[hash%uint32(len(s.locks))] + lock.RUnlock() +} + +// Lock 写锁加锁 +func (s *SegmentKeysLock) Lock(key string) { + hash := s.hash(key) + lock := s.locks[hash%uint32(len(s.locks))] + lock.Lock() +} + +// Unlock 写锁解锁 +func (s *SegmentKeysLock) Unlock(key string) { + hash := s.hash(key) + lock := s.locks[hash%uint32(len(s.locks))] + lock.Unlock() +} diff --git a/syncx/segment_key_lock_test.go b/syncx/segment_key_lock_test.go new file mode 100644 index 0000000..cb7d333 --- /dev/null +++ b/syncx/segment_key_lock_test.go @@ -0,0 +1,74 @@ +// Copyright 2021 ecodeclub +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package syncx + +import ( + "sync" + "sync/atomic" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestSegmentKeysLock(t *testing.T) { + s := NewSegmentKeysLock(10) + key := "test_key" + var wg sync.WaitGroup + wg.Add(2) + var writeDone atomic.Bool + var readStarted atomic.Bool + val := false + cond := sync.NewCond(&sync.Mutex{}) + cond.L.Lock() + + // 写 goroutine + go func() { + defer wg.Done() + s.Lock(key) + val = true // 加写锁写 + s.Unlock(key) + writeDone.Store(true) + cond.Broadcast() + }() + + // 读 goroutine + go func() { + defer wg.Done() + cond.L.Lock() + defer cond.L.Unlock() + + // 等待写操作完成 + for !writeDone.Load() { + cond.Wait() + } + + readStarted.Store(true) + cond.Broadcast() + s.RLock(key) + assert.Equal(t, true, val, "Read lock err") // 加读锁读 + defer s.RUnlock(key) + }() + + // 等待读操作开始 + for !readStarted.Load() { + cond.Wait() + } + + // 检查写操作是否已完成,防止意外情况导致读优先写发生 + assert.Equal(t, true, writeDone.Load(), "Write operation did not complete before read operation started") + + cond.L.Unlock() + wg.Wait() +}