10.4.2 RLock
使用Thread对象的Lock和RLock可以实现简单的线程同步,这两个对象都有acquire()方法和release()方法,对于那些每次只允许一个线程操作的数据,可以将其操作放到acquire()方法和release()方法之间。
RLock类对象是可重复锁,它可以被同一个线程多次获取,可以避免Lock多次锁定产生的死锁问题。acquire()和release()同样提供“锁定”和“解锁”功能。与Lock的区别在于,RLock可以嵌套调用锁定和解锁方法。
Lock和RLock的区别如下。
·threading.Lock:它是一个基本的锁对象,每次只能锁定一次,对于其余的锁请求,需等待锁释放后才能获取。
·threading.RLock:它代表可重入锁(reentrant lock)。对于可重入锁,在同一个线程中可以对它进行多次锁定,也可以多次释放。如果使用RLock,那么acquire()和release()方法必须成对出现。如果调用了n次acquire()加锁,则必须调用n次release()才能释放锁。
RLock具有可重入性。也就是说,同一个线程可以对已被加锁的RLock再次加锁,RLock对象会维持一个计数器来追踪acquire()方法的嵌套调用,线程在每次调用acquire()加锁后,都必须显式调用release()方法来释放锁。所以,一个被锁保护的方法可以调用另一个被相同锁保护的方法。
Lock是控制多个线程对共享资源进行访问的工具。通常,锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程在开始访问共享资源之前应先请求获得Lock对象。当对共享资源访问完成后,程序释放对Lock对象的锁定。
在实现线程安全的控制中,比较常用的是RLock。通常使用RLock的代码格式如下:
使用RLock对象来控制线程安全,当加锁和释放锁出现在不同的作用范围内时,通常建议使用finally块来确保在必要时释放锁。
通过使用Lock对象可以非常方便地实现线程安全的类,线程安全的类具有如下特征。
·该类的对象可以被多个线程安全地访问。
·每个线程在调用该对象的任意方法之后,都将得到正确的结果。
·每个线程在调用该对象的任意方法之后,该对象都依然保持合理的状态。
总的来说,不可变类总是线程安全的,因为它的对象状态不可改变,但可变对象需要额外的方法来保证其线程安全。
例如,用RLock进行线程同步来实现窗口卖票,可以重复上锁、解锁,但是上锁次数和解锁次数一定要相互对应。代码如下:
运行结果如下: