前面两篇文章,写了python线程同步原语的基本应用。下面这篇文章主要是经过阅读源码来了解这几个类的内部原理和是怎么协同一块儿工做来实现python多线程的。html
相关文章连接:python同步原语--线程锁 python
1、关于Condition类多线程
Condition的用法:函数
用来记录线程的状态变量ui
查看Condition的源码,会看到做者给开发者提供的文档说明。‘Class that implemets a condition variable’写得很明白,这是一个用来记录线程状态的类。url
1. Condition对象初始化spa
从这段代码能够看出,Condition使用了threading模块的Rlock类,关于Rlock的用法能够看我以前写的一篇文章python同步原语--线程锁 。在对象初始化的同时,将Rlock的请求锁和释放锁方法赋给了内部的self.acquire和self.release对象方法。当初始化对象同时初始化这两个方法,也就是说,每一个对象在实例化的时候都会实例一个新的可重入锁(RLock)。这样能够避免不一样对象(condition实例对象)间对类中共享方法的争夺,避免出现死锁的问题。.net
这段代码很是的重要。若是熟悉python的上下文管理的朋友应该一看就明白,这是上下文管理中的进入和退出操做。当在调用with时,程序会自动调用_ _enter_ _方法,在程序执行完毕,退出此上下文环境时,自动调用_ _exit_ _方法。那么在这里的_ _enter_ _和_ _exit_ _方法分别有什么用呢?经过阅读源码发现,前者是调用了Rlock的acquier方法(获取锁),然后者调用了Rlock的release方法(释放锁)。在下面我会继续讲这两个方法在类中的做用。线程
2. wait()方法
源码中对wait()方法的定义是‘Wait untified or until a timeout occurs’。意思是阻塞等待知道有提示(notify)或者超时时间(timeout)的到达。
再看看wait()函数的内部逻辑
_is_owned()方法是判断此Condition对象是否有获取到锁,若是没有获取到锁(多是可重入锁的获取次数已经达到预约值,不过这种状况不多发生),就会报出错误。接下来是对须要等待的程序进行一些列的处理。先是给这个程序分配锁,对它的程序空间和内部变量进行封锁。同时把这个加锁后的程序放进双端队列(deque)‘等待者们’中。
好像wait()方法的功能到此就结束了。可是注意到下面还有try函数块,旁边一行注释写着‘restore state no matter what’而后又举了一个KeyboardInterrupt的异常状况。意思是当出现了例如键盘输入ctrl+C这类操做的时候,程序如何退出阻塞。若是在调用wait方法的时候没有传入timeout参数,那么,等待者程序就会从新获取锁。若是有timeout参数,就会根据参数来肯定退出阻塞的时间。这就是为何咱们有时在输入ctrl+C强行退出阻塞的时候,程序会等待一下子才给出退出程序的提示的缘由。
3. notify()方法
接下来这个notify()方法在Condition类中也是很是的重要(queue模块内部也调用了这个函数)
notify()方法内部实现:
notify直接翻译过来就是‘提示’的意思。那么为何Condition对象须要‘提示’呢?阅读源码下来,其真正的功能不是提示,而是锁的释放,而且在释放了指定数量的waiters以后,顺便将他们从‘等待者们’队列中删除。若是直接理解为提示,就会很难理解了。但这是老外在定义函数时的写法,本人的理解是,有点像给阻塞的程序发出信号(提示),中止阻塞(释放锁),这么理解应该也算勉强解释得过去吧。
Condition内部另外还有一个notify_all()方法,这个方法对‘等待者们’队列中的全部的程序都发出‘提示’,释放锁,而没有像notify中那样有数量n的限制。
源码:
那么总结上面的Condititon内部的方法实现,能够看出,Condition类是为了实现一种状态的‘保存’,即在多线程编程的状况下,因为线程间共享空间而容易引起错误,每每须要让一些线程先执行,然后面的线程等待(阻塞)。那么若是这些程序须要阻塞等待,就会调用Condition类实例对象的wait方法,当结束等待的信号发出时,就会调用Condition的notify方法对队列中的程序进行释放锁操做。
2、关于Segmaphore和BoundedSegmaphore
若是在主机执行IO密集型任务的时候再执行这种短期内完成大量任务(多线程)的程序时,计算机就有很大可能会宕机。
这时候就能够为这段程序添加一个计数器(counter)功能,来限制一个时间点内的线程数量。当每次进行IO操做时,都须要向segmaphore请求资源(锁),若是没有请求到,就阻塞等待,请求成功才就像执行任务。
那么segmaphore的内部实现是怎样的呢?实质上segmaphore也是锁,其内部也是经过Lock和Condition实现的。Lock是单锁,而segmaphore是能够本身定义的多锁。在初始化segmaphore时,须要传入参数counter。当线程向segmaphore请求资源(锁)时,内部的counter会自动减1。当释放资源(锁)的时,counter就会自动加1。
segmaphore主要有两个方法,acquire()和release()方法。
1. acquire()方法
官方的定义:
def acquire(self, blocking=True, timeout=None):
当内部的counter(源码其实是用value变量保存)等于0的时候,其余线程acquire会阻塞。这个时候,以前向segmaphore发出请求并得到锁的线程,它们若是同时执行完任务并但愿释放锁时,那么锁的释放是随机的。任何一个完成任务的线程都会释放锁,这个顺序跟线程向请求的时间和任务完成的时间是没有任何关系的。
参数的解析:
1)blocking:默认为True,当线程请求不到资源的时候,会阻塞等待。若是设置为False,则线程请求不到资源时不会阻塞。
2)timeout:若是设置blocking = True,即默认值时,通过timeout时间会退出阻塞。
2. release()方法
这个方法与Lock的release方法很像,具体能够看看我以前写的关于锁的一篇文章。
源码:
解析:
当一个实例请求释放锁的时候,segmaphore内部的_value会自动加1,同时调用notify方法,将被锁住的线程‘唤醒’。
3、关于Event类
阅读源码知道,Event是也基于Condition和Lock实现的
1. set()方法
在 python--线程同步原语 这篇文章我曾经写过一个案例,在进程中调用一次event.set()函数就能够一次性通知(释放)全部阻塞的等待的锁。其内部实现的原理在这里,最关键的一个方法是notify_all()。
在调用set()方法的时候,方法内部会将_flags设置为True,即等待的事件会退出阻塞。
2. clear()方法
clear()方法不一样做太多解析了,就是内部的_flags从新设置为False
3. wait()方法
wait()方法的定义:
def wait(self, timeout=None):
内部实现:
原理仍是很简单的,实际上Event的wait()方法正是调用了Condition中的实例方法wait()。调用wait()方法的时候能够传入参数timeout(超时时间),做为Event事件自动退出阻塞的时间界限。