面试题答案
一键面试线程池设计
- 线程池大小确定:
- CPU 密集型任务:通常设置线程池大小为 CPU 核心数 + 1,这样可以在一个线程因偶尔的 I/O 阻塞等情况时,其他线程仍能充分利用 CPU 资源。例如,在一个 4 核 CPU 的机器上,线程池大小可设为 5。
- I/O 密集型任务:根据经验公式
线程数 = CPU 核心数 * (1 + 平均等待时间 / 平均工作时间)
。比如,若平均等待时间为 2s,平均工作时间为 1s,在 4 核 CPU 机器上,线程数 = 4 * (1 + 2 / 1) = 12。同时,可通过性能测试,逐步调整线程池大小,找到最优值。
- 任务队列管理:
- 使用
queue.Queue
作为任务队列。Queue
是线程安全的,适合在线程池场景下使用。 - 在任务入队时,使用
put
方法。例如:
from queue import Queue task_queue = Queue() task_queue.put(task)
- 在工作线程从队列取任务时,使用
get
方法,可设置超时时间,防止线程一直阻塞。例如:
try: task = task_queue.get(timeout = 5) except queue.Empty: pass
- 使用
- 线程复用机制:
- 使用
threading.Thread
类创建工作线程。每个工作线程进入一个循环,从任务队列中不断获取任务并执行。例如:
import threading def worker(task_queue): while True: try: task = task_queue.get(timeout = 5) task() task_queue.task_done() except queue.Empty: break for _ in range(thread_pool_size): t = threading.Thread(target = worker, args=(task_queue,)) t.start()
- 当任务完成后,通过
task_queue.task_done()
通知队列任务已处理完毕。Queue
的join
方法可用于阻塞主线程,直到队列中所有任务都被处理完。
- 使用
性能优化
- 动态调整线程池大小:
- 监控任务队列的长度和线程的忙碌状态。如果任务队列长时间不为空且线程都处于忙碌状态,适当增加线程池大小;如果线程长时间空闲,减少线程池大小。可以使用
threading.Condition
来实现线程间的同步与通知,以便动态调整线程数量。
- 监控任务队列的长度和线程的忙碌状态。如果任务队列长时间不为空且线程都处于忙碌状态,适当增加线程池大小;如果线程长时间空闲,减少线程池大小。可以使用
- 优化任务调度:
- 对于优先级不同的任务,可使用优先级队列(如
heapq
结合queue.Queue
实现优先级任务队列),优先处理高优先级任务。 - 将相关任务进行分组,例如按 I/O 操作类型分组,让同一组任务尽量在同一线程或少数线程上执行,减少线程切换开销。
- 对于优先级不同的任务,可使用优先级队列(如
- 资源管理与缓存:
- 对于频繁使用的资源(如数据库连接、文件句柄等),使用连接池或缓存机制。例如,使用
DBUtils
实现数据库连接池,减少每次获取连接的开销。 - 对于需要频繁读取的数据,可使用
functools.lru_cache
对函数结果进行缓存,减少重复计算。
- 对于频繁使用的资源(如数据库连接、文件句柄等),使用连接池或缓存机制。例如,使用
- 异步 I/O 结合:
- 在 Python 中,可结合
asyncio
进行异步 I/O 操作。对于一些支持异步的库(如aiohttp
用于 HTTP 请求),将其与线程池结合使用,进一步提高 I/O 效率。例如,在某些 I/O 任务中,主线程使用asyncio
管理异步任务,而将一些计算密集型的子任务放入线程池处理。
- 在 Python 中,可结合