python多线程编程中的Semaphore, 它内部维护了一个计数器,每一次acquire操作都会让计数器减1,每一次release操作都会让计数器加1,当计数器为0时,任何线程的acquire操作都不会成功,Semaphore确保对资源的访问有一个上限, 这样,就可以控制并发量
在多线程的系列里,我已经讲解了Lock,RLock,Condition,在讲解信号量之前,不妨先做一个简单的内容回顾
对一个变量进行加1操作,远比我们以为的要复杂,从内存到寄存器,执行加1,写回内存,这就引申出了多线程情况下对共享资源的访问的问题。有些操作是线程安全的,有些操作是线程不安全的,对于那些线程不安全的操作,我们必须为这个操作加上一把锁,来确保同一个时刻,只能有一个线程进行操作,就像加1的操作,要等到彻底执行完,也就是写回到内存后才允许其他线程进行操作
RLock的作用和Lock一样,不同的地方在于,它可以被一个线程多次请求,只要你保证每一个acquire对应一个release就可以保证线程不会发生死锁,它比Lock更加安全,尤其当程序复杂时,推荐你使用RLock
Condition,也称作条件变量,它也可以实现多线程对共享资源的互斥访问,相比于Lock,RLock,它更加灵活,其灵活之处在于它提供了wait操作和notify操作。
假设这样一个场景,你有2个生产者线程,10个消费者线程,当商品数量大于10个的时候,生产者不再生产,商品数量为0时,不再消费,如果是用RLock来做线程间互斥,当商品数量为0时,按道理说消费者线程是不进行任何消费活动的,但是,这些消费者线程仍然在拼命的尝试来获得锁,得到锁以后,发现商品数量为0,于是再释放掉锁,紧接着去争抢锁。
如果是用Condition来做呢,消费者发现商品数量为0时,可以进行wait操作,此时,线程进入等待状态,再没有被唤醒之前,它是不会去争抢锁的,极端的情况是商品为0时,10个消费者都进入到wait状态,而这时,生产者获得锁,生产了商品,然后进行notify操作,去唤醒一个线程,当然这次唤醒的可能是另一个生产者,不过没关系,总会又一次唤醒的是消费者线程。
condition和Lock,RLock相比较,在生产者和消费者模型中,避免了不必要的对锁的争抢,更加高效的调用线程资源。
来谈一谈Semaphore,它内部维护了一个计数器,每一次acquire操作都会让计数器减1,每一次release操作都会让计数器加1,当计数器为0时,任何线程的acquire操作都不会成功,Semaphore确保对资源的访问有一个上限, 这样,就可以控制并发量。
如果使用Lock,RLock,那么只能有一个线程获得对资源的访问,但现实中的问题并不总是这样,假设这样一个场景,一个线程安全的操作,同一个时刻可以允许两个线程进行,如果太多了效率会降低,那么Lock,Rlock,包括Condition就不适合这种场景。
我这里举一个不恰当的例子(因为我自己没这么干过,但有助于你理解)。假设你写了一个多线程爬虫,起10个线程去爬页面,10个线程访问过于频繁了,目标网站对你采取反爬措施。但你把线程数量降到2两个就没问题了。那么对于这个场景,你仍然可以启动10个线程,只是向目标网站发送请求的这个操作,你可以用Semaphore来控制,使得同一个时刻只有两个线程在请求页面
import threading
import time
semaphore = threading.Semaphore(2)
def worker(id):
print('thread {id} acquire semaphore'.format(id=id))
semaphore.acquire()
print('thread {id} get semaphore do something'.format(id=id))
time.sleep(2)
semaphore.release()
print('thread {id} release semaphore'.format(id=id))
for i in range(10):
t = threading.Thread(target=worker, args=(i, ))
t.start()
QQ交流群: 211426309