- 1. 简介
- 2. 全局解释器锁GIL
- 3. 多线程
- 4. 多进程
- 5. 异步IO
- 基础概念:
- 并行:利用多核CPU同时运行;
- 并发:伪并行,同一时刻一个线程运行时,其它线程挂起;
- 同步:提交一任务,等待返回值,再提交一个任务
- 异步:提交一个任务,不等待返回,再提交一个任务
- 阻塞:程序运行时,遇到IO程序挂起;
- 非阻塞:程序顺序执行,不会卡住;
- 任务类型:
类型 IO时间 CPU占用率 例子 CPU密集型短高压缩解压、加密解密、正则匹配 IO密集型长低文件读写、DB读写、网络请求 - 方法选择:
方法 优缺点 场景 多进程 Process
包 multiprocessing优点:可以利用多核CPU并行运算
缺点:占用资源最多、可启动数目比线程少CPU密集型多线程 Thread
包 threadingVS 进程-优点:占用资源少
VS 进程-缺点:只能并发执行,不能利用多CPU
VS 协程-缺点:启动数目有限,占用内存资源,有切换开销IO密集型
少量任务多协程 Coroutine
包 asyncio优点:内存开销最少、启动协程数量最多
缺点:支持的库有限制(aiohttp vs requests)、代码实现复杂IO密集型
大量任务
- Python速度慢原因:
- 动态解释型语言,边解释边执行;
- 全局解释器锁GIL,无法利用多核CPU实现并发。
- 全局解释器锁GIL:多线程之间的切换会导致引用计数器计数混乱、数据不完整、状态异常等问题。因此,提出GIL(Global Interpreter Lock)使得同一时刻也只有一个线程在执行。
- GIL限制下加速方法:
- 多线程:IO密集型任务,实现CPU和IO并行加速;CPU密集型任务,由于频繁的线程切换会拖慢速度。
- 多进程:利用多核CPU实现并行计算。
- 创建:
import threading def my_func(a, b): pass # 创建线程对象 t = threading.Thread(target=my_func, args=(100, 200)) t.start() # 启动线程 t.join() # 等待结束
- 生产者消费者架构:
- 例子:信件与邮递员
- 定义:生产者把数据放入缓冲区,消费者从缓冲区取出数据进行处理。
- 多线程数据通信:
import queue q = queue.Queue() # 创建队列缓冲区 q.put(item) # 生产者把数据放入缓冲区(阻塞的,满时卡住) item = q.get() # 消费者从缓冲区取出数据(阻塞的,空时卡住) q.qsize() # 查看缓冲区元素个数 q.empty() # 判断缓冲区是否为空 q.full() # 判断缓冲区是否已满
- 线程安全:多线程切换时,能够正确地处理多个线程之间的共享数据。
import threading lock = threading.Lock() # 解决方法1: lock.acquire() # do something lock.release() # 解决方法2: with lock: # do something
- 线程池:
- 线程生命周期:新建线程系统需要分配资源、终止线程系统需要回收资源。线程池可以重用线程,从而减去新建/终止的开销。
- 线程池优点:
- 提升性能:减去了大量新建、终止线程的开销,重用了线程资源;
- 防御功能:能有效避免系统因为创建线程过多,而导致系统负荷过大相应变慢等问题
- 代码优势:使用线程池的语法比自己新建线程执行线程更加简洁
- 适用场景:适合需要大量线程完成任务、但实际任务处理时间较短
- 实现:
from concurrent.futures import ThreadPoolExecutor, as_completed # 第一种: with ThreadPoolExecutor() as pool: results = pool.map(craw, urls) # map的结果和入参是顺序对应的 for result in results: print(result) # 第二种: with ThreadPoolExecutor() as pool: futures = [ pool.submit(craw, url) for url in urls ] for future in futures: # 顺序返回 print(future.result()) for future in as_completed(futures): # 谁先执行完,先返回谁 print(future.result())
- 线程生命周期:新建线程系统需要分配资源、终止线程系统需要回收资源。线程池可以重用线程,从而减去新建/终止的开销。
- 原理:多进程在多个CPU上并行执行,适用于CPU密集型计算。
- 对比梳理:
- 协程:使用超级循环在单线程内实现并发。
- 实现:request 不支持异步,需要使用aiohttp
import asyncio loop = asyncio.get_event_loop() # 获取事件循环 async def myfunc(url): # 定义协程 await get_url(url) # 对应IO,执行到此处不阻塞 tasks = [loop.create_task(myfunc(url)) for url in urls]# 创建任务列表 loop.run_until_complete(asyncio.wait(tasks)) # 执行等待完成
- 信号量:设置并发量Semaphore,控制并发数量。
sem = asyncio.Semaphore(10) # 方法1 async with sem: # work with shared resource # 方法2 await sem.acquire() try: # work with shared resource finally: sem.release()
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)