引言
前面一篇文章中,我们介绍了通过Python中的ThreadPoolExecutor,也就是线程池更加灵活便捷地实现了Python中的多线程编程。
在Python的多线程编程中,有一个默认的原则是:"如果多线程编程不可避免,那么请尽量使用ThreadPoolExecutor来实现"。足以看到,线程池的重要性。
那么本文在对线程池的使用有一定的基础之上,稍微深入一下线程池的内部设计及实现,从而加深对线程池的理解,相信后续对多线程编程的实践能够更加得心应手。
本文的主要内容有:
1、ThreadPoolExecutor的内部构成
2、线程池的执行机制
3、Future未来对象
ThreadPoolExecutor的内部构成
我们可以结合ThreadPoolExecutor的源码来看下该对象的内部构成:
从源码中可以得出以下结论:
1、内部维持一个任务队列,也就是_work_queue,持有一个SimpleQueue对象,用于存储待执行的任务,所有提交的任务都先添加进入该队列中。
2、内部维持一个线程池,是一个set(),用于控制同时工作的线程集合。工作线程都是Thread对象,负责任务的实际执行。
3、max_workers,用于控制工作线程集合的线程最大个数。默认为CPU核数 + 1,同时限制最大不超过32。
4、通过Future对象实现线程结果及异常的跨线程传递。
线程池的执行机制
线程池的执行流程主要涉及到以下环节:
1、创建线程池:通过ThreadPoolExecutor(max_workers)创建一个线程池。
2、提交任务:通过调用submit()方法提交可调用对象(或者函数)及其参数作为待执行任务,添加至任务队列中。
3、获取工作线程:线程池中的空闲的工作线程会从任务队列中获取任务。
4、执行任务:每个工作线程会调用提交的函数并传递相应的参数。执行结果会异步地封装进Future对象中。
5、处理结果:用户可以通过Future对象获取任务的执行结果、异常信息等。可以使用result()方法等待任务执行完成并获取结果。
6、关闭线程池:任务执行完毕后,可以调用shutdown(wait=True)方法来关闭线程池,不再接受新的任务提交,并等待已提交的任务执行完成再真正关闭。
相应的执行机制,可以参考下面简化的示意图:
Future未来对象
Future对象,也叫作未来对象,用于进行线程执行结果的异步传递。它提供了一种机制来处理尚未完成的操作,允许程序在等待结果时能够有效执行其他任务。
关于Future对象的设计,需要结合源码,稍微展开介绍一下:
从初始化方法中,可以看到Future对象的实现主要涉及到几个关键组件:
1、状态管理:通过_state属性维护当前工作线程的任务执行状态,状态主要有:PENDING、RUNNING、CANCELED、CANCELED_AND_NOTIFIED、FINISHED等。
2、结果的存储:一旦任务完成,工作线程会将相应的结果设置到Future对象中,通过_result属性维持。如果工作线程执行中发生异常,也会将异常信息回写到Future对象中,通过_exception属性维持。
3、回调机制:用户可以注册回调函数,实现任务完成自动执行相应的函数的逻辑。
Future对象支持的操作主要有:
1、result(timeout=None):如果任务已经完成,则返回其结果;如果没有完成,则阻塞等待;如果超时,则抛出TimeoutError异常。
2、exception(timeout=None):如果任务执行完成并且抛出了异常,则返回该异常;否则返回None。
3、done():返回一个布尔值,标识任务是否已经执行完成。
4、cancel():取消一个任务的执行(如果可能的话),如果任务已经开始执行,则无法取消,返回False。
5、add_done_callback(fn):添加回调函数,使得任务完成时,自动调用该函数。
总的来说,正是由于Future对象的设计理念围绕着简化异步操作和提高并发编程的可用性展开。通过提供非阻塞调用、异步处理和回调机制。Future对象使得开发者可以更加方便地管理异步计算的状态和结果。它是实现高效并发执行和任务管理的重要工具,对于需要处理大量异步任务的应用程序尤为关键。
总结
本文围绕着ThreadPoolExecutor和Future对象的源码,深入介绍了相应的内部实现,以及线程池任务的处理全流程。对Future对象的设计理念和基础操作,也做了相应的展开介绍。这些都是理解线程池并发编程的核心,希望您能有所收获。