
1. 为什么需要异步执行在Python开发中我们经常会遇到需要同时处理多个任务的场景。比如一个网络爬虫需要同时抓取多个页面或者一个Web服务器需要同时处理多个客户端请求。如果采用传统的同步执行方式程序会像排队一样逐个处理任务效率非常低下。我去年开发过一个电商价格监控系统最初就是用同步方式实现的。结果每次扫描100个商品页面要花费近5分钟完全达不到实时监控的要求。后来改用多线程后性能直接提升了20倍这就是异步执行的威力。2. Threading模块基础解析2.1 线程与进程的区别很多初学者容易混淆线程和进程的概念。简单来说进程是操作系统分配资源的基本单位线程是CPU调度的基本单位一个进程可以包含多个线程线程共享进程的内存空间在Python中由于GIL全局解释器锁的存在多线程更适合I/O密集型任务而不是CPU密集型任务。这也是为什么我的价格监控系统改用多线程后效果显著 - 因为主要时间都花在网络I/O等待上。2.2 创建线程的三种方式Python的threading模块提供了三种创建线程的方法直接创建Thread对象import threading def task(): print(子线程执行) t threading.Thread(targettask) t.start()继承Thread类class MyThread(threading.Thread): def run(self): print(子线程执行) t MyThread() t.start()使用线程池Python 3.2from concurrent.futures import ThreadPoolExecutor with ThreadPoolExecutor(max_workers5) as executor: executor.submit(task)在实际项目中我推荐使用第三种方式因为它更易于管理线程数量避免资源耗尽。3. 线程同步与通信3.1 锁机制当多个线程需要访问共享资源时必须考虑线程安全问题。我遇到过的一个典型场景是多线程写入同一个日志文件如果不加锁会导致日志内容错乱。Python提供了多种锁机制Lock基本锁RLock可重入锁Semaphore信号量Event事件通知import threading lock threading.Lock() shared_data 0 def increment(): global shared_data with lock: shared_data 1提示使用with语句可以确保锁一定会被释放避免死锁3.2 队列通信线程间通信的最佳实践是使用queue模块提供的队列from queue import Queue import threading q Queue() def producer(): for i in range(5): q.put(i) def consumer(): while True: item q.get() print(f消费: {item}) q.task_done() threading.Thread(targetproducer).start() threading.Thread(targetconsumer).start() q.join()我在一个爬虫项目中就用这种生产者-消费者模式一个线程负责抓取URL多个线程并行处理页面内容效率非常高。4. 实战多线程爬虫案例4.1 需求分析假设我们要开发一个简单的图片下载器需要从多个URL同时下载图片。同步方式下每个下载都要等待前一个完成显然效率太低。4.2 实现代码import threading import requests from queue import Queue import os class ImageDownloader: def __init__(self, max_threads5): self.queue Queue() self.max_threads max_threads self.lock threading.Lock() def add_url(self, url): self.queue.put(url) def download_image(self): while True: url self.queue.get() try: response requests.get(url, timeout10) filename os.path.basename(url) with open(filename, wb) as f: f.write(response.content) with self.lock: print(f下载完成: {filename}) except Exception as e: print(f下载失败: {url}, 错误: {e}) finally: self.queue.task_done() def start(self): for _ in range(self.max_threads): t threading.Thread(targetself.download_image, daemonTrue) t.start() self.queue.join() # 使用示例 downloader ImageDownloader(max_threads5) urls [ https://example.com/image1.jpg, https://example.com/image2.jpg, # 更多URL... ] for url in urls: downloader.add_url(url) downloader.start()4.3 性能优化技巧合理设置线程数量不是越多越好通常I/O密集型任务设置为CPU核心数的2-5倍使用会话(Session)复用连接session requests.Session() response session.get(url)添加超时处理避免个别慢请求阻塞整个队列实现断点续传记录已完成的任务5. 常见问题与调试技巧5.1 GIL的影响Python的全局解释器锁(GIL)会导致多线程在CPU密集型任务上表现不佳。如果你发现多线程没有带来性能提升可能是因为任务主要是CPU计算线程间有大量锁竞争解决方案改用多进程(multiprocessing)使用C扩展释放GIL考虑asyncio异步IO5.2 线程泄漏我曾经遇到过一个线上服务内存泄漏的问题最后发现是因为没有正确管理线程生命周期。要避免这种情况设置daemonTrue让线程随主线程退出使用ThreadPoolExecutor管理线程池监控线程数量threading.enumerate() # 查看所有活跃线程5.3 死锁调试死锁是多线程编程中最头疼的问题之一。我总结了一些调试技巧使用threading.current_thread().name给线程命名记录锁的获取顺序使用timeout参数避免无限等待lock.acquire(timeout5)可视化工具py-spy, threading模块的_get_ident()6. 进阶话题6.1 ThreadLocal数据每个线程有时需要维护自己的数据副本这时可以使用threading.local()local_data threading.local() def task(): local_data.value threading.current_thread().name print(local_data.value) threads [threading.Thread(targettask) for _ in range(3)] for t in threads: t.start() for t in threads: t.join()我在开发Web爬虫时就用这个特性来维护每个线程独立的代理设置和cookies。6.2 定时任务threading.Timer可以用于延迟执行def remind(): print(该喝水休息了) timer threading.Timer(3600, remind) # 1小时后提醒 timer.start()6.3 线程优先级Python的threading模块本身不提供优先级调度但可以通过queue.PriorityQueue实现类似效果from queue import PriorityQueue q PriorityQueue() q.put((1, 高优先级任务)) q.put((3, 低优先级任务)) q.put((2, 中优先级任务)) while not q.empty(): _, task q.get() print(f处理: {task})在实际项目中我发现合理设置任务优先级可以显著提升系统响应速度特别是对于实时性要求高的场景。