多线程编程之条件锁

342 阅读5分钟

1 基本介绍

Condition() 条件锁。条件锁是在递归锁的基础上增加了能够暂停线程运行的功能,并且我们可以使用wait()方法和notify()方法来控制线程执行的个数。条件锁可以自由设定一次放行的线程数目。 条件锁的内部有两把锁,一把是底层锁(同步锁),一把是高级锁(递归锁)底层锁的解锁方式有两种,使用wait()方法会暂时解开底层锁同时加上一把高级锁,只有当接收到别的线程里的notfiy()后才会解开高级锁和重新上锁底层锁,也就是说条件锁底层是根据同步锁和递归锁的不断切换来进行实现的

2 常用方法

条件锁相关的方法如下:

方法描述
threading.Condition()返回一个条件锁对象
threading.Condition().acquire(blocking=True, timeout=1)上锁,当一个线程在执行被上锁代码块时,将不允许切换到其他线程,默认失效时间为1秒
threading.Condition().release()解锁,当一个线程在执行未被上锁代码块时,将允许系统根据策略自行切换到其他线程中运行
threading.Condition().wait(timeout=None)将当前线程设置为“等待”状态,只有该线程接到“通知”或者超时时间到期之后才会继续运行,在“等待”状态下的线程将允许系统根据策略自行切换到其他线程中运行
threading.Condition().wait_for(predicate,timeout=None)将当前线程设置为“等待”状态,只有该线程的predicate返回一个True或者超时时间到期之后才会继续运行,在“等待”状态下的线程将允许系统根据策略自行切换到其他线程中运行。注意:predicate参数应当传入一个可调用对象,且返回结果为bool类型
lockObject.notify(n=1)通知一个当前状态为“等待”的线程继续运行,也可以通过参数n通知多个
lockObject.notify_all()通知所有当前状态为“等待”的线程继续运行

3 使用示例

以栈这种数据结构为例进行说明这个问题。一个线程负责产生数据,将数据压栈;另一个线程消费数据,将数据出栈。这两个线程互相依赖,当栈为空时,消费线程无法消费数据,应该通知生产线程产生数据;当栈已满,生产线程无法添加数据,应该通知消费线程取出数据。

example:

import threading
import time
​
condition = threading.Condition()
​
​
class Stack:
    def __init__(self):
        """
        栈
        """
        # 栈指针初值为0
        self.pointer = 0
        self.data = [-1, -1, -1, -1, -1]
​
    def push(self, save_data):
        """
        压栈方法
        :param save_data:要进行压栈的数据
        """
        global condition
        condition.acquire()
        # 栈已满,不能压栈
        while self.pointer == len(self.data):
            # 栈已满,生产线程无法添加数据
            condition.wait()
        # 栈已满,通知消费线程取出数据
        condition.notify()
        # 数据压栈
        self.data[self.pointer] = save_data
        # 指针向上移动
        self.pointer += 1
        condition.release()
​
    def pop(self):
        """
        出栈方法
        :return: 栈中的数据
        """
        global condition
        condition.acquire()
        # 栈已空,不能出栈
        while self.pointer == 0:
            # 当栈为空时,消费线程无法消费数据
            condition.wait()
        # 通知生产线程产生数据
        condition.notify()
        # 指针向下移动
        self.pointer -= 1
        # 数据出栈
        save_data = self.data[self.pointer]
        condition.release()
        return save_data
​
​
stack = Stack()
​
​
def producer_thread_body():
    """
    生产者线程体函数
    """
    global stack
    for num in range(10):
        stack.push(num)
        print(f'生产数据:{num}')
        time.sleep(1)
​
​
def consumer_thread_body():
    """
    消费者线程体函数
    """
    global stack
    for _ in range(10):
        x = stack.pop()
        print(f'消费:{x}')
        time.sleep(1)
​
​
def main():
    t1 = threading.Thread(target=producer_thread_body)
    t1.start()
    t2 = threading.Thread(target=consumer_thread_body)
    t2.start()
​
​
if __name__ == '__main__':
    main()

result:

生产数据:0
消费:0
生产数据:1消费:1生产数据:2消费:2生产数据:3
消费:3
生产数据:4消费:4生产数据:5
消费:5
生产数据:6
消费:6
生产数据:7
消费:7
生产数据:8
消费:8
生产数据:9消费:9

从运行结果来看,总是生产在前,消费在后,这说明线程间通信是成功的。

如果线程间不进行通信(即注释掉上述代码中和condition有关的语句),执行结果如下:

生产数据:0
消费:0
消费:-1
生产数据:1
消费:1
生产数据:2
消费:2
生产数据:3
消费:3
生产数据:4
生产数据:5
消费:5
消费:4
生产数据:6
生产数据:7
消费:7
生产数据:8
消费:8
消费:6生产数据:9

-1表示数据还没有被生产出来,这是消费线程消费了尚未产生的数据,这是很不合理的。

4 with语句

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

import threading
import time
​
condition = threading.Condition()
​
​
class Stack:
    def __init__(self):
        """
        栈
        """
        # 栈指针初值为0
        self.pointer = 0
        self.data = [-1, -1, -1, -1, -1]
​
    def push(self, save_data):
        """
        压栈方法
        :param save_data:要进行压栈的数据
        """
        global condition
        with condition:
            # 栈已满,不能压栈
            while self.pointer == len(self.data):
                # 栈已满,生产线程无法添加数据
                condition.wait()
            # 栈已满,通知消费线程取出数据
            condition.notify()
            # 数据压栈
            self.data[self.pointer] = save_data
            # 指针向上移动
            self.pointer += 1
​
    def pop(self):
        """
        出栈方法
        :return: 栈中的数据
        """
        global condition
        with condition:
            # 栈已空,不能出栈
            while self.pointer == 0:
                # 当栈为空时,消费线程无法消费数据
                condition.wait()
            # 通知生产线程产生数据
            condition.notify()
            # 指针向下移动
            self.pointer -= 1
            # 数据出栈
            save_data = self.data[self.pointer]
            return save_data
​
​
stack = Stack()
​
​
def producer_thread_body():
    """
    生产者线程体函数
    """
    global stack
    for num in range(10):
        stack.push(num)
        print(f'生产数据:{num}')
        time.sleep(1)
​
​
def consumer_thread_body():
    """
    消费者线程体函数
    """
    global stack
    for _ in range(10):
        x = stack.pop()
        print(f'消费:{x}')
        time.sleep(1)
​
​
def main():
    t1 = threading.Thread(target=producer_thread_body)
    t1.start()
    t2 = threading.Thread(target=consumer_thread_body)
    t2.start()
​
​
if __name__ == '__main__':
    main()

result:

生产数据:0
消费:0
生产数据:1
消费:1
生产数据:2
消费:2
生产数据:3
消费:3
生产数据:4
消费:4
生产数据:5
消费:5
生产数据:6
消费:6
生产数据:7
消费:7
生产数据:8消费:8生产数据:9消费:9