Python线程应用程序中的提供的低级的线程控制工具有thread module,一些为了简化多线程应用的开发程序员,用Python语言在thread的基础上构建了一个高级的线程控制库——threading。
在这一节中,我们将剖析threading的具体实现。在剖析threading的具体实现之前,我们先来看看threading是如何使用的。我们知道通过threading.Thread创建多线程,有两个阶段,第一阶段是调用threading.Thread.start,而第二阶段是在threading.Thread.start中调用threading.Thread.run。
当处于第一阶段时,还没有调用thread.start_new_thread创建原生子线程,这时候线程记录在_limbo中。由于没有创建子线程,所以现在没有线程id,记录的方式为_limbo[thread] = thread。
在第二阶段,已经成功地调用thread. start_new_thread创建了原生子线程,这时将从_limbo中删除子线程,而将子线程记录到_active中,记录的方式为_active[thread_id] = thread。可见。
Python这两个dict分别维护了已经创建和等待创建的子线程集合。对这两个dict的访问_active_limbo_ lock的保护之下进行。在threading module中,提供了列举当前所有子线程的操作:threading. enumerate。这个操作很简单,就是将_active和_limbo中维护的线程集合的信息输出。
在thread module中,Python提供了用户级的线程同步工具:Lock对象。而在threading module中,Python提供了不同的用于线程同步的工具。以简化Python线程应用程序序。这些threading中的线程同步工具实际上都是建立在thread所提供的Lock对象的基础上的。
通过调用threading.Lock,我们就可以创建一个thread中的Lock对象,如前面所描述的,在这个对象上,我们可以进行acquire、release等操作。在threading中的其他线程同步工具都是在这个Lock对象的基础上,下面我们将对这些线程同步工具做一个概述性的介绍,具体的实现请读者参阅threading.py。
RLock对象是Lock对象的一个变种,其内部维护着一个Lock对象,但是它是一种可重入的Lock。一般地,对于Lock对象而言,如果一个线程连续两次进行acquire操作。那么由于第一次acquire之后没有release,第二次acquire将挂起线程,这将直接导致Lock对象永远不会release,因此线程死锁。
RLock对象允许一个线程多次对其进行acquire操作,因为在其内部通过一个counter变量维护着线程acquire的次数。而且每一次的acquire操作必须有一个release操作与之对应,在所有的release操作都完成之后,别的线程才能申请该RLock对象。
Python线程应用程序对象是对Lock对象的包装,在创建Condition对象时,其构造函数需要一个Lock对象作为参数,如果没有这个Lock对象参数,Condition将在内部自行创建一个Rlock对象。
在Condition对象上,当然也可以调用acquire和release操作,因为内部的Lock对象本身就支持这些操作。但是Condition的价值在于其提供的wait和notify的语义。假设有Condition对象C,当线程A调用C.wait()时,线程A将释放C中的Lock对象,并进入阻塞状态。
直到有别的线程调用C.notify(),A才会重新通过acquire申请C中的Lock对象,并退出wait操作。Semaphore对象内部维护着一个Condition对象,对于管理一组共享资源非常有用。Lock对象可以保护一个共享资源,但是假如我们有一个共享资源池,其中有5个共享资源A。
这意味着可以有5个线程同时自由地访问这些资源,然而如果使用Lock来对共享资源进行保护的话,所有的线程都将互斥,这使得有4个资源A被浪费了。Semaphore正是在Condition的基础上实现的对共享资源池进行保护的线程同步机制。Semaphore提供了两个操作:acquire和release,都具有与Lock相同的语义。
当线程调用Semaphore. acquire时,如果共享资源池中还有剩余的A时,线程就会继续执行;而如果资源池中已经没有任何资源存在了,线程就会将自身挂起,直到别的线程调用Semaphore.release释放一个资源。
与Semaphore类似,Event对象实际上也是对Condition对象的一种包装,只是提供了独有的set和wait语义。Event类的代码很简单,有兴趣的读者可以参考threading.py。在thread3.py中我们看到,threading中一个关键的组件是threading.Thread,在这一节中我们来看一看它的具体实现。在threading.Thread的实现中,你会发现我们前面提到的许多机制。
【编辑推荐】