101、Python并发编程:Condition实现“排排坐分果果”的复杂同步

引言

前面介绍了Python并发编程中,可以使用互斥锁或者可重入锁来实现多线程之间的同步。但是,直接使用锁的同步,主要是对临界区的简单保护,确保任意时刻只有一个线程进入临界区操作临界资源。

由于多线程的调度涉及到时间片轮转、GIL等,执行的顺序是无法精确控制的。

如果想要实现更加精确的线程执行顺序的控制,类似于多线程交替执行的效果,可以考虑使用Condition。

本文的主要内容有:

1、需求场景:排排坐,分果果

2、使用锁的效果

3、使用Condition真正实现需求

4、Condition的实现原理及使用注意事项

需求场景:排排坐,分果果

假设我们有这样一个需求场景:

1、总共有10部质量参差不齐的小米15

2、张三和李四平分这10部手机

3、为了尽量公平,俩人轮流进行挑选,每人每次选一部,然后由下一个人来选,直至所有的手机瓜分完成。

接下来我们通过锁和Condition来分别尝试实现该需求场景。

使用锁的效果

直接看代码:

from threading import Thread, Lock
import time


class PeakXiaoMi(Thread):
    def __init__(self, name, count, lock):
        super().__init__()
        self.name = name
        self.count = count
        self.lock = lock

    def run(self):
        for i in range(self.count):
            with self.lock:
                print(f'{self.name} 第{i + 1}次 选手机')
                time.sleep(0.1)


if __name__ == '__main__':
    lock = Lock()
    zhangsan = PeakXiaoMi('张三', 5, lock)
    lisi = PeakXiaoMi('李四', 5, lock)
    zhangsan.start()
    lisi.start()
    zhangsan.join()
    lisi.join()

对代码做一个简单说明:

1、通过继承Thread类实现自定义选手机的线程,初始化方法中,传入的参数:name表示姓名,count表示每人可以选的个数,lock就是尝试同步步调的锁。

2、通过重载run()方法,实现选手机的业务逻辑,通过一个延时模拟效果。

执行结果如下:

b8371e35faf6c659bf6ee5cbe3e6077d.jpeg

从执行结果中,我们可以看到明显的不公平出现了。实际上,如果我们不进行延时的设置,会更加不公平,可能的结果是张三调了5个之后,李四才开始选。

9cf7767aa5cb46e15a16a71fdc59a3f7.jpeg

显然,通过锁试图实现两个线程实现交替执行的线程同步,是无法达到预期的效果的。

使用Condition真正实现需求

接下来,通过Condition来尝试实现该需求。

直接看代码以及执行效果,之后再来解释Condition的使用

from threading import Thread, Condition


class PeakXiaoMi(Thread):
    def __init__(self, name, count, condition, first=True):
        super().__init__()
        self.name = name
        self.count = count
        self.condition = condition
        self.first = first

    def run(self):
        for i in range(self.count):
            with self.condition:
                if self.first:
                    print(f'{self.name} 第{i + 1}次 选手机')
                    self.condition.notify()
                    self.condition.wait()
                else:
                    self.condition.wait()
                    print(f'{self.name} 第{i + 1}次 选手机')
                    self.condition.notify()


if __name__ == '__main__':
    cond = Condition()
    zhangsan = PeakXiaoMi('张三', 5, cond)
    lisi = PeakXiaoMi('李四', 5, cond, first=False)
    # 注意,这里后选的,涉及到wait()先调用,所以需要先启动,否则会死锁
    lisi.start()
    zhangsan.start()
    zhangsan.join()
    lisi.join()

程序的执行效果:

a0c92187a0b84425d240cac58416a7e4.jpeg

可以看到,真正实现了“排排坐,分果果,我一个,你一个”的效果。

Condition的实现原理及使用注意事项

Condition的实现原理

958e894d30a94ed95f4a3ed0766e2653.jpeg

f790eae03f72942c69797c739bb707ac.jpeg

从上面的文档中可以看出Condition的实现原理:

1、每个Condition对象包含一个可重入锁(RLock),也可以在创建时传递一个锁对象。用于控制对共享资源的访问。这个内部锁保证了条件变量的操作是原子的。

2、Condition对象实现了__enter__()和__exit__()这两个上下文管理的魔术方法,所以,可以使用with语法糖来使用Condition。

3、Condition对象中维护了一个等待队列,用于记录当前等待该条件的线程。当条件满足时,可以通过notify()方法或者notify_all()方法来唤醒等待的线程。

Condition对象最核心的方法有:

1、wait()方法:将调用wait()方法的线程加入到等待该条件的等待队列中。

2、notify()方法:从等待队列中唤醒一个等待的线程。

3、notify_all()方法:唤醒等待队列中的所有线程。

使用注意事项

Condition可以实现更加精准的线程同步机制,但是,在使用过程中,需要特别注意一些关键点,从而确保程序能够正确、安全地执行。

1、Condition通常用来保护共享资源,当要访问共享资源时需要获取Condition内部的锁,以避免数据竞争和资源争用。所以在访问或修改共享资源之前,务必先获取锁。

2、为了确保锁的获取和释放能够正确执行,应当尽量使用with语句来管理,这样可以方便地在代码块退出时实现锁资源的自动释放,即便有异常发生。

3、多个线程之间的复杂交互,有可能导致死锁。所以,在设计时应当尽量小心,确保所有需要获取的锁按固定顺序进行获取和释放,避免交叉请求多个锁的情况。

4、Condition.wait()方法可以接收一个timeout参数,用于表示超时自动退出等待的场景。处理超时情况时,必须清晰定义超时后的操作逻辑。

3ce9eabc3fbb48d75a97051600dce189.jpeg

总结

本文简单介绍了Condition的内部设计原理、Condition的核心方法,以及在使用Condition实现多线程的复杂同步需求时的注意事项。

以上,就是本文的全部内容了,希望对您有所帮助!

a79db63cf18d4a67c9e490175c71b404.jpeg

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

南宫理的日知录

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值