python--基础知识点--threading.condition()条件变量

1. 同步

当线程在系统中运行时,线程的调度具有一定的透明性,通常程序无法准确控制线程的轮换执行,如果有需要,Python 可通过线程通信来保证线程协调运行。

同步:多个线程协按照一定的顺序协同完成某一任务

为了实现同步,可以借助于 Condition 对象来保持协调。使用 Condition 可以让那些己经得到 Lock 对象却无法继续执行的线程释放 Lock 对象,Condition 对象也可以唤醒其他处于等待状态的线程。

2. condition

Condition 类提供了如下几个方法:

  • acquire([timeout])/release():调用 Condition 关联的 Lock 的 acquire() 或 release() 方法。
  • wait([timeout]):导致当前线程进入 Condition 的等待池等待(挂起)通知并释放锁,直到其他线程调用该 Condition 的 notify() 或 notify_all() 方法来唤醒该线程。在调用该 wait() 方法时可传入一个 timeout 参数,指定该线程最多等待多少秒。
  • notify():唤醒在该 Condition 等待池中的单个线程并通知它,收到通知的线程将自动调用 acquire() 方法尝试加锁。如果所有线程都在该 Condition 等待池中等待,则会选择唤醒其中一个线程,选择是任意性的。
  • notify_all():唤醒在该 Condition 等待池中等待的所有线程并通知它们。

将 Condition 对象与 Lock 对象组合使用,可以为每个对象提供多个等待集(wait-set)。因此,Condition 对象总是需要有对应的 Lock 对象。从 Condition 的构造器 init(self, lock=None) 可以看出,程序在创建 Condition 时可通过 lock 参数传入要绑定的 Lock 对象;如果不指定 lock 参数,在创建 Condition 时它会自动创建一个与之绑定的 Lock 对象。

3. 示例1

假设系统中有两个线程,这两个线程分别代表存款者和取钱者,现在假设系统有一种特殊的要求,即要求存款者和取钱者不断地重复存款、取钱的动作,而且要求每当存款者将钱存入指定账户后,取钱者就立即取出该笔钱。不允许存款者连续两次存钱,也不允许取钱者连续两次取钱。

本例程序中,可以通过一个旗标来标识账户中是否已有存款,当旗标为 False 时,表明账户中没有存款,存款者线程可以向下执行,当存款者把钱存入账户中后,将旗标设为 True,并调用 Condition 的 notify() 或 notify_all() 方法来唤醒其他线程。

当存款者线程进入线程体后,如果旗标为 True,就调用 Condition 的 wait() 方法让该线程等待。当旗标为 True 时,表明账户中已经存入了钱,取钱者线程可以向下执行,当取钱者把钱从账户中取出后,将旗标设为 False,并调用 Condition 的 notify() 或 notify_all() 方法来唤醒其他线程;当取钱者线程进入线程体后,如果旗标为 False,就调用 wait() 方法让该线程等待。

本程序为 Account 类提供了 draw() 和 deposit() 两个方法,分别对应于该账户的取钱和存款操作。因为这两个方法可能需要并发修改 Account 类的 self.balance 成员变量的值,所以它们都使用 Lock 来控制线程安全。除此之外,这两个方法还使用了 Condition 的 wait() 和 notify_all() 来控制线程通信。

# Account.py
import threading


class Account:
    # 定义构造器
    def __init__(self, account_no, balance):
        # 封装账户编号、账户余额的两个成员变量
        self.account_no = account_no
        self._balance = balance
        self.cond = threading.Condition()
        # 定义代表是否已经存钱的旗标
        self._flag = False

    # 因为账户余额不允许随便修改,所以只为self._balance提供getter方法
    def getBalance(self):
        return self._balance

    # 提供一个线程安全的draw()方法来完成取钱操作
    def draw(self, draw_amount):
        # 加锁,相当于调用Condition绑定的Lock的acquire()
        self.cond.acquire()
        try:
            # 如果self._flag为假,表明账户中还没有人存钱进去,取钱方法阻塞
            if not self._flag:
                self.cond.wait()
            else:
                # 执行取钱操作
                print(threading.current_thread().name + " 取钱:" + str(draw_amount))
                self._balance -= draw_amount
                print("账户余额为:" + str(self._balance))
                # 将标识账户是否已有存款的旗标设为False
                self._flag = False
                # 唤醒其他线程
                self.cond.notify_all()
        # 使用finally块来释放锁
        finally:
            self.cond.release()

    def deposit(self, deposit_amount):
        # 加锁,相当于调用Condition绑定的Lock的acquire()
        self.cond.acquire()
        try:
            # 如果self._flag为真,表明账户中已有人存钱进去,存钱方法阻塞
            if self._flag:            # ①
                self.cond.wait()
            else:
                # 执行存款操作
                print(threading.current_thread().name + " 存款:" + str(deposit_amount))
                self._balance += deposit_amount
                print("账户余额为:" + str(self._balance))
                # 将表示账户是否已有存款的旗标设为True
                self._flag = True
                # 唤醒其他线程
                self.cond.notify_all()
        # 使用finally块来释放锁
        finally:
            self.cond.release()

上面程序使用 Condition 的 wait() 和 notify_all() 方法进行控制,对存款者线程而言,当程序进入 deposit() 方法后,如果 self._flag 为 True,则表明账户中已有存款,程序调用 Condition 的 wait() 方法被阻塞;否则,程序向下执行存款操作,当存款操作执行完成后,系统将 self._flag 设为 True,然后调用 notify_all() 来唤醒其他被阻塞的线程。如果系统中有存款者线程,存款者线程也会被唤醒,但该存款者线程执行到 ① 号代码处时再次进入阻塞状态,只有执行 draw() 方法的取钱者线程才可以向下执行。同理,取钱者线程的运行流程也是如此。

程序中的存款者线程循环 100 次重复存款,而取钱者线程则循环 100 次重复取钱,存款者线程和取钱者线程分别调用 Account 对象的 deposit()、draw() 方法来实现。主程序可以启动任意多个“存款”线程和“取钱”线程,可以看到所有的“取钱”线程必须等“存款”线程存钱后才可以向下执行,而“存款”线程也必须等“取钱”线程取钱后才可以向下执行。主程序代码如下:

import threading
import Account


#  定义一个函数,模拟重复max次执行取钱操作
def draw_many(account, draw_amount, max):
    for i in range(max):
        account.draw(draw_amount)


#  定义一个函数,模拟重复max次执行存款操作
def deposit_many(account, deposit_amount, max):
    for i in range(max):
        account.deposit(deposit_amount)


# 创建一个账户
acct = Account.Account("1234567", 0)
# 创建、并启动一个“取钱”线程
threading.Thread(name="取钱者", target=draw_many, args=(acct, 800, 10)).start()
# 创建、并启动一个“存款”线程
threading.Thread(name="存款者甲", target=deposit_many, args=(acct, 800, 10)).start()
# 创建、并启动一个“存款”线程
threading.Thread(name="存款者乙", target=deposit_many, args=(acct, 800, 10)).start()

运行该程序,可以看到存款者线程、取钱者线程交替执行的情形,每当存款者向账户中存入 800 元之后,取钱者线程就立即从账户中取出这笔钱。存款完成后账户余额总是 800 元,取钱结束后账户余额总是 0 元。

运行该程序,将会看到如图 1 所示的结果。
在这里插入图片描述

从图 1 中可以看出,3 个存款者线程随机地向账户中存钱,只有 1 个取钱者线程执行取钱操作。只有当取钱者线程取钱后,存款者线程才可以存钱;同理,只有等存款者线程存钱后,取钱者线程才可以取钱。

图 1 显示程序最后被阻塞无法继续向下执行。这是因为 3 个存款者线程共有 300 次尝试存钱操作,但 1 个取钱者线程只有 100 次尝试取钱操作,所以程序最后被阻塞。

如图 1 所示的阻塞并不是死锁,对于这种情况,取钱者线程已经执行结束,而存款者线程只是在等待其他线程来取钱而已,并不是等待其他线程释放同步监视器。不要把死锁和程序阻塞等同起来。

4. 示例2

threading.Condition()是Python标准库中用于线程同步的条件变量类。它提供了一种基于锁的机制,允许线程在满足特定条件之前等待,并在条件满足时通知其他线程。

下面是一个简单的示例,演示如何使用threading.Condition()来实现线程间的同步:

import threading

# 创建一个共享资源
shared_resource = []
# 创建一个条件变量
condition = threading.Condition()

# 生产者线程函数
def producer():
    global shared_resource
    with condition:
        # 等待条件满足
        while not shared_resource:
            condition.wait()
        # 生产新的数据
        new_data = "New Data"
        shared_resource.append(new_data)
        print("Producer produced:", new_data)
        # 通知其他线程条件已满足
        condition.notify()

# 消费者线程函数
def consumer():
    global shared_resource
    with condition:
        # 等待条件满足
        while not shared_resource:
            condition.wait()
        # 消费数据
        consumed_data = shared_resource.pop(0)
        print("Consumer consumed:", consumed_data)
        # 通知其他线程条件已满足
        condition.notify()

# 创建并启动生产者和消费者线程
producer_thread = threading.Thread(target=producer)
consumer_thread = threading.Thread(target=consumer)
producer_thread.start()
consumer_thread.start()

# 等待线程结束
producer_thread.join()
consumer_thread.join()python

在这个示例中,我们创建了一个共享资源shared_resource,它是一个空列表。我们还创建了一个条件变量condition。

生产者线程通过调用condition.wait()来等待条件满足。在这里,我们使用了一个循环来确保只有在满足条件时才继续执行。当条件不满足时,线程会被阻塞,并释放内部的锁以允许其他线程执行。只有当其他线程调用condition.notify()时,该线程才会被唤醒。

消费者线程的逻辑与生产者线程类似,也是通过调用condition.wait()来等待条件满足。当条件满足时,它会消费共享资源中的数据。

最后,我们创建并启动生产者和消费者线程。使用join()方法等待线程结束,以确保主线程等待所有子线程完成。

总结起来,threading.Condition()提供了一种简单而有效的机制来实现线程间的同步和通信。通过使用condition.wait()等待条件满足、condition.notify()通知其他线程,我们可以在多线程环境中实现线程之间的协作。

  • 2
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值