示例:锁

锁是一种同步机制,用于保证一项资源在任何时候只能被一个进程使用,如果有其他进程想要使用相同的资源,那么就必须等待,直到正在使用资源的进程放弃使用权为止。

一个锁的实现通常会有获取(acquire)和释放(release)这两种操作:

获取操作用于取得资源的独占使用权。在任何时候,最多只能有一个进程取得锁,我们把成功取得锁的这个进程称为锁的持有者。在锁已经被持有的情况下,所有尝试再次获取锁的操作都会失败。

释放操作用于放弃资源的独占使用权,一般由锁的持有者调用。在锁被释放之后,其他进程就可以再次尝试获取这个锁了。

代码清单2-2 展示了一个使用字符串键实现的锁程序,这个程序会根据给定的字符串键是否有值来判断锁是否已经被获取,而针对锁的获取操作和释放操作则是分别通过设置字符串键和删除字符串键来完成的。

代码清单2-2 使用字符串键实现的锁程序:/string/lock.py

        VALUE_OF_LOCK = "locking"

       class Lock:

           def__init__(self, client, key):
                self.client = client
                self.key = key

           defacquire(self):
                """
                尝试获取锁。成功时返回True,失败时返回False
                """
                result = self.client.set(self.key, VALUE_OF_LOCK, nx=True)
                returnresult isTrue

           defrelease(self):
                """
                尝试释放锁。成功时返回True,失败时返回False
                """
                returnself.client.delete(self.key) == 1

获取操作acquire()方法是通过执行带有NX选项的SET命令来实现的:

        result = self.client.set(self.key, VALUE_OF_LOCK, nx=True)

NX选项的值确保了代表锁的字符串键只会在没有值的情况下被设置:

如果给定的字符串键没有值,那么说明锁尚未被获取,SET命令将执行设置操作,并将result变量的值设置为True。

如果给定的字符串键已经有值了,那么说明锁已经被获取,SET命令将放弃执行设置操作,并将result变量的值设置为None。

acquire()方法最后会通过检查result变量的值是否为True来判断自己是否成功取得了锁。

释放操作release()方法使用了之前没有介绍过的DEL命令,这个命令接受一个或多个数据库键作为参数,尝试删除这些键以及与之相关联的值,并返回被成功删除的键的数量作为结果:

        DEL key [key ...]

因为Redis的DEL命令和Python的del关键字重名,所以在redis-py客户端中,执行DEL命令实际上是通过调用delete()方法来完成的:

        self.client.delete(self.key) == 1

release()方法通过检查delete()方法的返回值是否为1来判断删除操作是否执行成功:如果用户尝试对一个尚未被获取的锁执行release()方法,那么方法将返回false,表示没有锁被释放。

在使用DEL命令删除代表锁的字符串键之后,字符串键将重新回到没有值的状态,这时用户就可以再次调用acquire()方法去获取锁了。

以下代码演示了这个锁的使用方法:

        >>> fromredisimportRedis
        >>> fromlockimportLock
        >>> client = Redis(decode_responses=True)
        >>> lock = Lock(client, 'test-lock')
        >>> lock.acquire()  # 成功获取锁
        True
        >>> lock.acquire()  # 锁已被获取,无法再次获取
        False
        >>> lock.release()  # 释放锁
        True
        >>> lock.acquire()  # 锁释放之后可以再次被获取
        True

虽然代码清单2-2中展示的锁实现了基本的获取和释放功能,但它并不完美:

因为这个锁的释放操作无法验证进程的身份,所以无论执行释放操作的进程是否为锁的持有者,锁都会被释放。如果锁被持有者以外的其他进程释放,那么系统中可能会同时出现多个锁,导致锁的唯一性被破坏。

这个锁的获取操作不能设置最大加锁时间,因而无法让锁在超过给定的时限之后自动释放。因此,如果持有锁的进程因为故障或者编程错误而没有在退出之前主动释放锁,那么锁就会一直处于已被获取的状态,导致其他进程永远无法取得锁。

本书后续将继续改进这个锁的实现,使得它可以解决这两个问题。