使用Redis实现的读写锁,在使用时,只有2个返回值:
0,nil 可正常往下执行了
1,nil 超出了锁定最大重试次数
package redistool
import (
"github.com/fwhezfwhez/errorx"
"github.com/garyburd/redigo/redis"
"time"
)
type RedisRWMutex struct {
key string
maxlocksecond int // 锁定状态标记的最大时间
maxretryTimes int // 最大阻塞重试次数
retryInteval time.Duration // 重试的间隔
}
func NewRedisRWMutex(key string, maxlocksecond int, maxretrytimes int, retryInteval time.Duration) *RedisRWMutex {
return &RedisRWMutex{
key: key,
maxlocksecond: maxlocksecond,
maxretryTimes: maxretrytimes,
retryInteval: retryInteval,
}
}
func (m *RedisRWMutex) RLock(conn redis.Conn) (int, error) {
maxRetry := m.maxretryTimes
rs, e := m.rlockloop(&maxRetry, conn)
if e != nil {
return 0, errorx.Wrap(e)
}
return rs, nil
}
func (m *RedisRWMutex) rlockloop(retryTimes *int, conn redis.Conn) (int, error) {
// 写锁定时, 锁状态置为2, 阻塞其他读写
// 读锁定时,锁状态置为1,不做任何阻塞
// 无锁时,锁状态为0,或者不存在该key
// 返回3表示需要阻塞
// 返回2表示可执行
var script = `
local stat = redis.call('GET',KEYS[1]);
-- 不存在,无锁时,返回可执行,并标记为读锁中
if not stat then
redis.call('SETEX', KEYS[1],ARGV[1],1)
return 2;
end
-- 存在,但是出于无锁状态,返回可执行,标记为读锁中
if tonumber(stat) == 0 then
redis.call('SETEX', KEYS[1],ARGV[1],1)
return 2;
end
-- 写锁定时,返回阻塞
if tonumber(stat) == 2 then
return 3;
end
-- 读锁定时,返回放行
if tonumber(stat) == 1 then
return 2;
end
-- 预期之外的结果
return 4;
`
vint, e := redis.Int(conn.Do("eval", script, 1, m.key, m.maxlocksecond))
if e != nil {
return 0, errorx.Wrap(e)
}
// 可执行
if vint == 2 {
return 0, nil
}
if vint == 3 {
*retryTimes --
if *retryTimes == 0 {
return 1, nil
}
time.Sleep(m.retryInteval)
return m.rlockloop(retryTimes, conn)
}
return 0, errorx.NewFromStringf("unexpected lock stat return %d", vint)
}
func (m *RedisRWMutex) RUnLock(
conn redis.Conn) {
conn.Do("del", m.key)
}
// 0 可执行
// 1 超出了最大重试次数了
func (m RedisRWMutex) Lock(conn redis.Conn) (int, error) {
var max = m.maxretryTimes
rs, e := m.loopLock(&max, conn)
if e != nil {
return 0, errorx.Wrap(e)
}
return rs, nil
}
func (m *RedisRWMutex) loopLock(
retryTimes *int,
conn redis.Conn) (int, error) {
// 写锁定时, 锁状态置为2, 阻塞其他读写
// 读锁定时,锁状态置为1,不做任何阻塞
// 无锁时,锁状态为0,或者不存在该key
// 返回3表示需要阻塞
// 返回2表示可执行
var script = `
local stat = redis.call('GET',KEYS[1]);
-- 无锁时,返回可执行,并标记为写锁中
if not stat then
redis.call('SETEX', KEYS[1],ARGV[1],2)
return 2;
end
-- 无锁,返回可执行,标记为写锁中
if math.abs(tonumber(stat)) < 0.1 then
redis.call('SETEX', KEYS[1],ARGV[1],2)
return 2;
end
-- 写锁定时,返回阻塞
if math.abs(tonumber(stat)-2) < 0.1 then
return 3;
end
-- 读锁定时,返回阻塞
if math.abs(tonumber(stat)-1) < 0.1 then
return 3;
end
-- 预期之外的结果
return 4;
`
vint, e := redis.Int(conn.Do("eval", script, 1, m.key, m.maxlocksecond))
if e != nil {
return 0, errorx.Wrap(e)
}
// 可执行
if vint == 2 {
return 0, nil
}
if vint == 3 {
*retryTimes --
if *retryTimes == 0 {
return 1, nil
}
time.Sleep(m.retryInteval)
return m.loopLock(retryTimes, conn)
}
return 0, errorx.NewFromStringf("unexpected lock stat return %d", vint)
}
func (m *RedisRWMutex) UnLock(conn redis.Conn) {
conn.Do("del", m.key)
}
测试用例
执行测试用例时,注意并发度太高超过了连接池连接数设定。
func TestRWMutextRLock(t *testing.T) {
var num = 26
wg := sync.WaitGroup{}
wg.Add(num)
var a = 0
var l = NewRedisRWMutex("locka", 15, 10, 10*time.Millisecond, )
for i := 0; i < num; i++ {
go func(i int ) {
defer wg.Done()
conn := RedisPool.Get()
defer conn.Close()
switch i % 2 {
case 0:
l.RLock(conn)
defer l.RUnLock(conn)
fmt.Println(a)
case 1:
l.Lock(conn)
defer l.UnLock(conn)
a++
// fmt.Println(a)
}
}(i)
}
wg.Wait()
fmt.Println("最后结果:", a)
}