Python 基于Redis实现一个分布式可重入锁

1、简述出现背景

Python 基于Redis实现一个简单的分布式锁
前面实现了一个简单的分布式锁,它在某些场景下可以有效的处理并发写数据问题。但是如果我们在某个线程中需要二次或多次获得同一个锁,这个分布式锁就会出现死锁,一直等待第一个加锁逻辑结束后才能继续进行,并且会直接导致当前的业务逻辑的异常和超时。因此还需要实现一种分布式可重入锁,来应对这种场景。

2、简述原理

简述一下可重入锁实现原理:首先在加锁时,进行判断是否能获得锁,如果获得锁,那么此时维护一个计数器,并在获得锁时设置计数器值为0。然后如果是同一个线程再次请求获得锁,那么就将计数器加一。在线程释放锁时有两种情况,第一种是重入时的释放锁,此时将维护的计数器减一即可。第二种是当计数器为0,且确实时目标线程释放锁时,此时真正的释放掉锁。流程结束。

3、代码参考
# redis_return_lock.py
import redis
import time
import threading

# 连接池方式
# pool = redis.ConnectionPool(host='127.0.0.1',port=6379)
pool = redis.ConnectionPool().from_url('redis://127.0.0.1:6379/2')
con = redis.Redis(connection_pool=pool)


class RedisLock(object):
    def __init__(self):
        self.redis_con = con

    def get_lock(self, val):
        while True:
            res = self.redis_con.set('lock', val, nx=True, ex=60)
            if res:
                self.redis_con.set(val, '0', nx=True, ex=60)
                break
            else:
                old_val = self.redis_con.get('lock')
                if old_val.decode() == val:
                    self.redis_con.incr(val)
                    break
                # print('继续等待锁...')
                time.sleep(0.1)

    def del_lock(self, val):
        old_val = self.redis_con.get('lock')
        num = self.redis_con.get(val)
        if old_val == val.encode() and num.decode() == '0':
            self.redis_con.delete('lock')
            print('锁释放成功')
        elif old_val == val.encode() and num.decode() != '0':
            print('重入锁内释放锁,计数器减一...')
            self.redis_con.decr(val)
SUMS = 0

def test_lock(name, num, val):
    try:
        lock = RedisLock()
        print('%s 开始工作' % name)
        print('%s 准备获取锁并加锁' % name)
        lock.get_lock(val)
        def inner_test(lock):
            lock.get_lock(val)
            print('测试重入锁...')
            lock.del_lock(val)
        inner_test(lock)
        print('%s 得到锁,继续工作' % name)
        global SUMS
        SUMS += 15
        time.sleep(num)
        print(SUMS)
    except Exception as e:
        print('发生异常:%s' % str(e))
    finally:
        print('%s 操作完成,准备释放锁'%name)
        lock.del_lock(val)


if __name__ == '__main__':
    start_time = time.time()
    tasks = []
    for num in range(1,4):
        t = threading.Thread(target=test_lock, args=('任务%d'%num,num,'lock%d'%num,))
        tasks.append(t)
        t.start()
    [item.join() for item in tasks]
    print('总耗时:', time.time() - start_time)
4、运行测试
python redis_return_lock.py

结果:

任务1 开始工作
任务1 准备获取锁并加锁
任务2 开始工作        
任务2 准备获取锁并加锁
任务3 开始工作        
任务3 准备获取锁并加锁
测试重入锁...
重入锁内释放锁,计数器减一...
任务1 得到锁,继续工作
15
任务1 操作完成,准备释放锁
锁释放成功
测试重入锁...
重入锁内释放锁,计数器减一...
任务3 得到锁,继续工作
30
任务3 操作完成,准备释放锁
锁释放成功
测试重入锁...
重入锁内释放锁,计数器减一...
任务2 得到锁,继续工作
45
任务2 操作完成,准备释放锁
锁释放成功
总耗时: 6.0716187953948975
可重入的分布式锁是指一个线程可以多次获取同一把锁而不会造成死锁。在分布式环境下,需要使用分布式锁来保证多个节点之间的数据同步。Redis可以通过SETNX命令实现分布式锁,但是没有原生支持可重入性。下面是一个使用Redis实现可重入的分布式锁的示例代码: ```python import redis class RedisReentrantLock: def __init__(self, redis_client, lock_key): self.redis_client = redis_client self.lock_key = lock_key self.thread_local = threading.local() self.lock_count = 0 def acquire(self): current_thread = threading.current_thread() if getattr(self.thread_local, 'lock_owner', None) == current_thread: self.lock_count += 1 return True else: if self.redis_client.setnx(self.lock_key, current_thread.ident): self.thread_local.lock_owner = current_thread self.lock_count = 1 return True else: return False def release(self): current_thread = threading.current_thread() if getattr(self.thread_local, 'lock_owner', None) == current_thread: if self.lock_count > 1: self.lock_count -= 1 else: self.redis_client.delete(self.lock_key) del self.thread_local.lock_owner self.lock_count = 0 return True else: return False ``` 这个类实现了acquire和release方法来获取和释放锁。如果一个线程已经获取了锁,再次获取锁时,acquire方法会增加锁计数器。当锁计数器减至0时,会释放锁。这样实现了可重入性。同时,使用了Python的threading.local类来确保每个线程都有自己独立的锁计数器。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

haeasringnar

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值