面试题答案
一键面试设计线程池思路
- 确定线程池大小:
- 对于I/O密集型任务,一般可以设置线程数为CPU核心数的数倍。例如,在一个4核心的CPU上,可设置线程数为8 - 16。公式可参考:
threads = (2 * cpu_count()) + 1
,这里cpu_count()
是获取CPU核心数的函数。这样设置是因为I/O操作会有大量等待时间,更多线程可在等待I/O时利用CPU资源。
- 对于I/O密集型任务,一般可以设置线程数为CPU核心数的数倍。例如,在一个4核心的CPU上,可设置线程数为8 - 16。公式可参考:
- 处理任务队列:
- 使用
queue.Queue
来创建任务队列。线程池中的线程从队列中获取任务并执行。当任务提交到线程池时,将任务放入这个队列。例如:
from queue import Queue task_queue = Queue()
- 使用
- 线程的复用:
- 创建一定数量的线程,这些线程在启动后进入一个循环,不断从任务队列中获取任务并执行。执行完一个任务后,不销毁线程,而是继续从队列获取新任务,从而实现线程复用。示例代码如下:
import threading def worker(): while True: task = task_queue.get() if task is None: break task() task_queue.task_done() for _ in range(thread_count): t = threading.Thread(target = worker) t.start()
使用cProfile
进行性能分析和调优
- 关键代码片段:
- 假设我们有一个简单的任务函数
io_bound_task
,和线程池管理的代码:
import time import queue import threading import cProfile def io_bound_task(): time.sleep(1) # 模拟I/O操作 return def worker(): while True: task = task_queue.get() if task is None: break task() task_queue.task_done() task_queue = queue.Queue() thread_count = 10 for _ in range(thread_count): t = threading.Thread(target = worker) t.start() for _ in range(100): task_queue.put(io_bound_task) task_queue.join() for _ in range(thread_count): task_queue.put(None)
- 假设我们有一个简单的任务函数
- 分析步骤:
- 在Python脚本中添加如下代码来使用
cProfile
进行性能分析:
cProfile.run(''' def io_bound_task(): time.sleep(1) return def worker(): while True: task = task_queue.get() if task is None: break task() task_queue.task_done() task_queue = queue.Queue() thread_count = 10 for _ in range(thread_count): t = threading.Thread(target = worker) t.start() for _ in range(100): task_queue.put(io_bound_task) task_queue.join() for _ in range(thread_count): task_queue.put(None) ''')
- 运行脚本后,
cProfile
会输出每个函数的执行时间、调用次数等信息。重点关注io_bound_task
函数的执行时间和调用次数,以及线程获取任务、执行任务等相关操作的时间。 - 根据分析结果进行调优,比如如果发现任务执行时间过长,可以优化
io_bound_task
中的具体I/O操作;如果发现线程获取任务等待时间过长,可以调整任务队列的管理方式等。
- 在Python脚本中添加如下代码来使用