python多线程线程同步---Condition

生产者与消费者模型是最为常见的多线程编程问题, 本文使用python语言利用Condition线程同步技术来解决生产者与消费者问题, 在这个过程中, 你将学习了解到多线程如何进行同步

1. 生产者-消费者模型

生产者与消费者模型是最为常见的多线程编程问题,它可以简要的总结为以下内容:

  1. 有一个商品池,存储商品
  2. 有若干个生产者,当商品池中商品的数量小于某个值时开始生产商品并放入到商品池中
  3. 有若干个消费者,当商品池中商品的数量大于某个值时开始消费商品

商品池中的商品数量是动态变化的,学习生产者和消费者模型,你需要关心以下几个问题:

  1. 生产者和消费者都会操作商品池,那么它必须是线程安全的,在多线程下访问共享资源,不出现数据不一致或者污染数据
  2. 生产者和消费者之间如何协调工作

2. 线程同步

线程同步的意思不是几个线程同时进行某个操作,而是指线程之间协同步调,这个“同”字是协同的意思,而不是同时的意思,在本例的生产者与消费者模型中,我希望能做到这样的同步(你也可以设计你的同步方式,如何同步,不是固定死的套路):

  1. 一个生产者生产商品后,能够进入到等待状态,由其他生产者或者消费者进行生产或消费操作

  2. 一个消费者消费商品后,能够进入到等待状态,由其他消费者或者生产者进行消费或生产操作

3. 条件变量--Condition

Condition被称为条件变量,除了提供与Lock类似的acquire和release方法外,还提供了wait和notify方法,如果你已经对锁有一定了解,那么,你对acquire和release肯定不陌生了,我重点介绍一下wait和notify方法

3.1 wait

调用这个方法,会释放掉底层的锁,那么看来,此前,一定是得到锁了。没错,使用condition的第一步就是调用acquire方法,这一步已经获得了锁。我们可以认为condition对象维护了一个锁,如果你不指定这个锁,默认是RLock锁,同时还维护了一个waiting池,调用wait方法后,waitting池会记录这个线程,同时,这个线程也进入到了阻塞状态,直到超时或者别的线程唤醒它。

当这些线程被唤醒以后,会重新试图去获得锁

3.2 notify

这个方法会唤醒处于waiting状态的线程,能唤醒多少个呢?这取决于传入的参数,如果不传,默认唤醒其中一个,如3.1中所说,被唤醒的这个线程会再次acquire锁,得到锁以后继续执行

4. 示例代码

代码并非我原创,而是从网上抄录下来的一篇,我重点来解释代码

import threading
import time

condition = threading.Condition()
products = 12


class Producer(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)

    def run(self):
        global condition, products
        while True:
            if condition.acquire():
                if products < 10:
                    products += 1;
                    print("Producer(%s):deliver one, now products:%s" %(self.name, products))
                    condition.notify()

                else:
                    print("Producer(%s):already 10, stop deliver, now products:%s" %(self.name, products))
                    condition.wait();
                condition.release()
                time.sleep(2)

class Consumer(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)

    def run(self):
        global condition, products
        while True:
            if condition.acquire():
                if products > 1:
                    products -= 1
                    print("Consumer(%s):consume one, now products:%s" %(self.name, products))
                    condition.notify()
                else:
                    print("Consumer(%s):only 1, stop consume, products:%s" %(self.name, products))
                    condition.wait()
                condition.release()
                time.sleep(2)

if __name__ == "__main__":
    for p in range(0, 2):
        p = Producer()
        p.start()

    for c in range(0, 10):
        c = Consumer()
        c.start()

4.1 生产者都调用了wait方法

在这个例子中,我把products的初始值设置为12,这样,两个生产者启动以后,都会调用wait方法,我这样设计的目的是想告诉你,wait的作用是释放掉底层的锁,只有这样,消费者线程启动以后,再调用acquire时才会成功获得锁,千万不要误以为wait会一直把持锁,实际上它释放了锁,然后等待被唤醒

4.2 notify

因为一次只有一个线程能够得到这把锁,其他线程都处于waiting状态,因此不论是生产者也好,还是消费者也罢,在自己完成生产或消费活动后,都要调用notify方法,唤醒其他线程,假设A线程调用了notify,唤醒了B线程,那么B线程会立刻获得锁么?答案是不会

因为此时,锁依然掌握在A线程手中,要注意,notify方法并不会归还锁,它只是唤醒其他线程,要等到A线程release时才会真正的释放掉锁,这时,B线程才会得到锁,如果A线程调用的是nofity(3),那么同时有3个线程被唤醒,这3个线程会争抢锁,最终也只有一个会获得锁

4.3 wait 和 release

我期初对这两个东西是比较困惑的,他们都释放了底层的锁,那么调用完了wait,是不是就可以不用再调用release了呢?答案是不可以

wait的确是释放了底层锁,这个作用和release是一样的,但是,调用wait后,线程就阻塞在那里了,一直等到被唤醒才会继续执行之后的代码,可以被唤醒就意味着要去获得锁,如果得不到锁,会继续尝试获得锁,如果得到锁,就必须用release来释放掉锁

严格的讲,一个acquire必须对应一个release,他们必须成对出现,切记

扫描关注, 与我技术互动

QQ交流群: 211426309

加入知识星球, 每天收获更多精彩内容

分享日常研究的python技术和遇到的问题及解决方案