一 、基本概念
以下概念都是在 Python 环境下
Sync
同步编程Async
异步 ,是指在外观上看来程序不会等待,而是找出可执行的操作/任务/线程 继续执行Asyncio
单个主线程 多个不同的任务task
,这些future
对象 被event loop
对象控制,就好像多线程版本的多个线程GIL
全局解释器锁Global Interpret Lock
Task
任务- 并发
Concurrency
同一时刻只有一个任务/线程 在执行,只不过会不停的切换这些任务 - 并行
Parallelism
多个进程同一个时刻一起执行
二、用法
- Asyncio
import asyncio
import aiohttp
import time
async def download_one(url):
async with aiohttp.ClientSession() as session:
async with session.get(url) as resp:
save resp
async def download_all(sites):
tasks = [asyncio.create_task(download_one(site)) for site in sites]
await asyncio.gather(*tasks)
def main():
sites = [
...
...
]
asyncio.run(download_all(sites))
if __name__ == '__main__':
main()
- Futures 多线程 线程池执行
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
executor.map(download_one, sites)
- Futures 多进程执行
with concurrent.futures.ProcessPoolExecutor(max_workers=int(max_workers)) as executor:
results = executor.map(open_rebuild_url, df.tolist())
GIL
说到 GIL
,首先要明确,GIL
是 Python
中 的一个全局解释锁,是 Python
设计解释器 Cpython
的一个技术术语,用于在解释器执行Python 线程时,锁住当前运行的线程,防止其他线程执行。在这个基础上, 你就明白为什么说,Python 是单线程
的语言了,因为在GIL 的限制下,只能同时执行一个线程,所谓的多线程实际上是,线程获取和释放 GIL 的切换过程,看起来像 多线程 。
GIL最初,是因为Python的解释器是 Cpython ,GIL 的目的是:
- 内存管理的
race condition
资源竞争 ,数据混乱,线程安全的问题 - C语言库大部分不是
原生线程安全
的
Python 中 对 GIL 的 释放和获取
是 通过 Cpython 解释器的check_interval
,Cpython 会轮询GIL 的状态,每隔一段时间,就会强制释放当前线程锁住的GIL ,给与别的线程执行的机会。
早期的轮询机制是
- 100 ticks = 1000bytecodes
- Python3 + interval 是 15 ms 轮询一次
Cpython 在 Py3 + 之后,15ms 线程强制释放GIL ,这样对CPU密集型更加的友好,比之前的 bytecodes 要避免很多的切换次数了,但是同一时间仍然只能执行一个线程,所以依旧很慢,多核的多进程,会有CPU0 释放 GIL ,CPU 1,2,3 ,0 竞争线程资源的问题,所以实际上造成了新的问题,线程颠簸 thrashing ,导致效率更低。
所以,多核CPU ,要想快,还是要用 多进程执行 优于 多线程 。这样的意义在于,每个进程都有独立的GIL ,不会发生线程切换的干扰和竞争,实现真正的并行。
那么如果避免 GIL 带来的线程切换的瓶颈呢?
-
例如 数据分析 常用库 numpy 的 矩阵运算,是由C实现的Python 库 ,所以不需要cpython 解释器,自然不受 GIL的影响
-
绕过Cpython 比如用其他Jpython 等其他实现 ,即,在 Java 代码中执行 Python 代码的方法 ,或者核心(在线程切换造成瓶颈的代码)用C++ 实现按,比如机器学习,AI,人脸识别 等工作的核心代码,基本都是用C++辅助实现的 。
靠脚本语言,去解决CPU密集问题,本身就是一个不切实际的想法,python和c 30倍的性能差距 摆在那里。在实际的工作中,非理论的现实中,很少有靠脚本语言去解决 cpu 密集操作的,而io 密集的高阻塞操作的解决方案,已经有很多了,比如 futures io wait asyncio