面试题答案
一键面试系统架构层面
- 合理划分任务:
- 将不同类型的任务分配到不同的线程或线程池中。例如,I/O 密集型任务和 CPU 密集型任务分开处理。对于 I/O 密集型任务,如文件读写、网络请求等,可以使用较多的线程,因为这些任务在等待 I/O 操作完成时会释放 GIL(全局解释器锁,在 CPython 中存在),使其他线程有机会执行。而 CPU 密集型任务,由于 GIL 的存在,使用过多线程可能会增加线程切换开销,所以可以使用较少的线程。
- 采用生产者 - 消费者模型。通过队列(如
queue.Queue
)来解耦生产数据和消费数据的线程。生产者线程将数据放入队列,消费者线程从队列中取出数据进行处理。这样可以避免多个线程直接竞争共享资源,同时提高整体的并行性。
- 分布式架构:
- 如果项目规模足够大,可以考虑将部分功能拆分成分布式服务。使用分布式系统框架(如 Celery),将任务分发到不同的服务器节点上执行。这样可以避免在单台机器上过多线程竞争资源的问题,并且可以充分利用多台机器的计算资源。
资源分配层面
- 减少共享资源:
- 尽量避免多个线程同时访问共享资源。如果可能,将数据进行复制,让每个线程有自己独立的数据副本。例如,在多线程处理数据时,每个线程可以从主数据集中获取一份数据,在自己的副本上进行操作,最后再将结果合并。
- 对于必须共享的资源,尽量缩小其作用域。比如将共享变量的定义放在需要使用它的最小代码块内,这样可以减少其他线程访问它的机会。
- 资源预分配:
- 在程序启动时,预先分配好线程可能需要的资源。例如,预先分配一定数量的内存块供线程使用,避免在运行过程中多个线程同时申请资源而产生竞争。对于文件操作,可以预先打开文件并分配文件描述符给不同的线程,避免线程在运行时竞争打开文件。
同步机制层面
- 锁机制:
- 互斥锁(
threading.Lock
):使用互斥锁来保护共享资源。当一个线程获取到互斥锁时,其他线程就不能访问被保护的资源,直到该线程释放锁。例如,在对共享变量进行读写操作时,使用互斥锁包裹代码块:
import threading lock = threading.Lock() shared_variable = 0 def modify_shared_variable(): global shared_variable lock.acquire() try: shared_variable += 1 finally: lock.release()
- 信号量(
threading.Semaphore
):如果共享资源有一定的数量限制,可以使用信号量。信号量内部维护一个计数器,每次获取信号量时计数器减 1,释放信号量时计数器加 1。当计数器为 0 时,其他线程无法获取信号量,从而限制了同时访问共享资源的线程数量。例如,假设有一个数据库连接池,最多允许 5 个线程同时使用连接:
import threading semaphore = threading.Semaphore(5) def use_database_connection(): semaphore.acquire() try: # 使用数据库连接的代码 pass finally: semaphore.release()
- 读写锁(
threading.RLock
或threading.Condition
结合实现读写锁功能):对于读多写少的场景,使用读写锁可以提高性能。多个线程可以同时获取读锁进行读操作,但只有一个线程可以获取写锁进行写操作,并且在写锁被获取时,其他线程不能获取读锁。在 Python 中,可以通过threading.Condition
来实现读写锁功能,示例代码如下:
import threading class ReadWriteLock: def __init__(self): self.mutex = threading.Lock() self.readers = 0 self.write_lock = threading.Condition(self.mutex) def acquire_read(self): self.mutex.acquire() try: self.readers += 1 if self.readers == 1: self.write_lock.acquire() finally: self.mutex.release() def release_read(self): self.mutex.acquire() try: self.readers -= 1 if self.readers == 0: self.write_lock.release() finally: self.mutex.release() def acquire_write(self): self.write_lock.acquire() def release_write(self): self.write_lock.release()
- 互斥锁(
- 队列同步:
- 使用
queue.Queue
进行线程间通信和同步。Queue
本身是线程安全的,它提供了put
和get
方法,这些方法在多线程环境下可以安全地使用。例如,在生产者 - 消费者模型中,生产者线程调用queue.put
方法将数据放入队列,消费者线程调用queue.get
方法从队列中取出数据,Queue
会自动处理同步问题,避免资源竞争。
- 使用
- 线程本地存储(
threading.local
):- 使用
threading.local
来创建线程本地的数据。每个线程对threading.local
对象的属性访问都是独立的,互不干扰。这可以避免线程之间共享数据导致的资源竞争问题。例如:
import threading local_data = threading.local() def thread_function(): local_data.value = 0 local_data.value += 1 print(f"Thread {threading.current_thread().name} has local value: {local_data.value}")
- 使用