多线程编程之递归锁

220 阅读2分钟

1 基本介绍

RLock递归锁是同步锁的一个升级版本,在同步锁的基础上可以做到连续重复多次acquire()后再多次realease()的操作。但是一定要注意加锁次数和解锁次数必须一致,否则也将引发死锁现象。 递归锁的内部维护着一个计数器,当计数器不为0的时候不能被I/O操作和时间轮询机制切换

2 常用方法

递归锁相关的方法如下:

方法描述
threading.RLock()返回一个递归锁对象
threading.RLock().acquire(blocking=True, timeout=1)上锁,当一个线程在执行被上锁代码块时,将不允许切换到其他线程,默认失效时间为1秒
threading.RLock().release()解锁,当一个线程在执行未被上锁代码块时,将允许系统根据策略自行切换到其他线程中运行
threading.RLock().locked()判断该锁对象是否处于上锁状态,返回一个布尔值

3 使用示例

以下是递归锁使用的一个案例。这里是将前面同步锁中造成死锁的代码拿来使用递归锁进行运行。

import threading
import time
import random
​
​
class TicketDB:
    def __init__(self):
        """
        车票数量
        """
        self.ticket_count = 5
​
    def get_ticket_count(self):
        """
        获取当前车票的数量
        :return: 当前车票的数量
        """
        return self.ticket_count
​
    def sell_ticket(self):
        # TODD:用户购票付款流程
        sleep_time = random.randrange(1, 8)
        time.sleep(sleep_time)
        t = threading.current_thread()
        print(f'{t.name}网点,已经售出第{self.ticket_count}张票')
        self.ticket_count -= 1
​
​
db = TicketDB()
lock = threading.RLock()
​
​
def thread_body():
    global db, lock
    while True:
        lock.acquire()
        lock.acquire()
        lock.acquire()
        curr_ticket_count = db.get_ticket_count()
        if curr_ticket_count > 0:
            db.sell_ticket()
        else:
            lock.release()
            lock.release()
            lock.release()
            break
        lock.release()
        lock.release()
        lock.release()
        time.sleep(1)
​
​
def main():
    t1 = threading.Thread(target=thread_body)
    t1.start()
    t2 = threading.Thread(target=thread_body)
    t2.start()
​
​
if __name__ == '__main__':
    main()

result:

Thread-1网点,已经售出第5张票
Thread-2网点,已经售出第4张票
Thread-1网点,已经售出第3张票
Thread-2网点,已经售出第2张票
Thread-1网点,已经售出第1张票

可以看到。同步锁中造成死锁的代码在递归锁下可以正常执行。

4 with语句

与同步锁一样,threading.RLock()对象中也实现了enter()exit()方法,因此我们也可以像同步锁一样,使用with语句进行上下文管理形式的递归锁的加锁解锁操作。

示例如下:

import threading
import time
import random
​
​
class TicketDB:
    def __init__(self):
        """
        车票数量
        """
        self.ticket_count = 5
​
    def get_ticket_count(self):
        """
        获取当前车票的数量
        :return: 当前车票的数量
        """
        return self.ticket_count
​
    def sell_ticket(self):
        # TODD:用户购票付款流程
        sleep_time = random.randrange(1, 8)
        time.sleep(sleep_time)
        t = threading.current_thread()
        print(f'{t.name}网点,已经售出第{self.ticket_count}张票')
        self.ticket_count -= 1
​
​
db = TicketDB()
lock = threading.RLock()
​
​
def thread_body():
    global db, lock
    while True:
        with lock:
            curr_ticket_count = db.get_ticket_count()
            if curr_ticket_count > 0:
                db.sell_ticket()
            else:
                break
            time.sleep(1)
​
​
def main():
    t1 = threading.Thread(target=thread_body)
    t1.start()
    t2 = threading.Thread(target=thread_body)
    t2.start()
​
​
if __name__ == '__main__':
    main()

result:

Thread-1网点,已经售出第5张票
Thread-1网点,已经售出第4张票
Thread-1网点,已经售出第3张票
Thread-2网点,已经售出第2张票
Thread-2网点,已经售出第1张票