01.协程
https://www.cnblogs.com/xiaonq/p/7905347.html#i4
1.1 什么是协程
1.1.1 协程的特点
协程是一种用户态的轻量级线程,由用户代码自行管理,而不是操作系统调度
- 非抢占式调度,协程只有在显式 yield 或 await 时才会主动让出 CPU。
- 单线程执行,避免了线程切换的锁竞争问题,适用于 IO 密集型任务。
1.1.2 协程的优点
- 切换开销极低:协程切换仅涉及栈和寄存器的切换,不需要系统调用。
- 无锁编程:协程在同一个线程中执行,不会有数据竞争问题,避免了线程同步的开销。
- 适用于高并发 IO:如异步 Web 服务器(FastAPI、Node.js)、网络爬虫等。
1.2 协程优缺点
-
协程缺点
- 无法利用多核资源:
协程的本质是个单线程,它不能同时将 单个CPU 的多个核用上
,协程需要和进程配合才能运行在多CPU上 协程如果阻塞掉,整个程序都阻塞
- 无法利用多核资源:
-
协程最大的优点
- 不仅是处理高并发(单线程下处理高并发)
- 特别节省资源(协程本质是一个单线程,当然节省资源)
- 500日活,用php写需要两百多态机器,但是golang只需要二十多太机器
1.3 协程遇到I/O切换,那活只谁干的?
-
简单说法
- 协程遇到I/O后自动切换,但是会保持一个socket连接,交给系统内核去处理工作
- epoll()就工作内核中,他维护了一个链表,来存放所有的socket连接
- 当内核处理完成后就会回调一个函数,以socket文件描述符为key,结果为value存放到字典中
- 此时这个列表还是在内核中,需要将这个字典拷贝到用户空间(用户进程中)
-
本质
- 1.
epoll()中内核则维护一个链表
,epoll_wait直接检查链表是不是空就知道是否有文件描述符准备好了。 - 2.在内核实现中epoll是根据每个sockfd上面的与设备驱动程序建立起来的回调函数实现的。
- 3.某个sockfd上的事件发生时,与它对应的回调函数就会被调用,来把这个sockfd加入链表,其他处于“空闲的”状态的则不会。
- 4.epoll上面链表中获取文件描述,这里使用内存映射(mmap)技术,避免了复制大量文件描述符带来的开销
- 内存映射(mmap):内存映射文件,是由一个文件到一块内存的映射,将不必再对文件执行I/O操作
- 1.
1.4 适用场景:
- 高并发 IO 任务:(如异步 Web 服务器、爬虫)。
- 事件驱动型编程:(如游戏引擎、GUI 事件循环)。
- 异步数据库访问: (如 asyncpg、aiomysql)。
1.5 Python中协程的模块
- greenlet:遇到I/O
手动切换
,是一个C模块 - gevent:对greenlet封装,遇到I/O
自动切换
(借助C语言库greenlet
) - asyncio:和gevent一样,也是实现协程的一个模块(
python自己实现
)
02.进程,线程,协程爬取页面
- 特点:
1.进程
:启用进程非常浪费资源2.线程
:线程多,并且在阻塞过程中无法执行其他任务3.协程
:gevent只用起一个线程,当请求发出去后gevent就不管,永远就只有一个线程工作,谁先回来先处理
2.1 for循环
- 第四:性能最差
1 | import requests |
2.2 进程池
缺点:
启用进程非常浪费资源
2.2.1 multiprocessing.Pool
1 | # -*- coding: utf-8 -*- |
2.2.2 ProcessPoolExecutor
1 | import requests |
2.3 线程池
缺点:
创建一个新线程将消耗大量的计算资源,并且在阻塞过程中无法执行其他任务。例:
比如线程池中10个线程同时去10个url获取数据,当数据还没来时这些线程全部都在等待,不做事。
1 | import requests |
2.4 协程
特点 :
gevent只用起一个线程,当请求发出去后gevent就不管,永远就只有一个线程工作,谁先回来先处理
1 | import gevent |
3、结论
- 如果任务是 CPU 密集型(如计算、AI 训练),建议使用多进程来充分利用多核 CPU。
- 如果任务是 IO 密集型(如网络请求、数据库查询),协程是更高效的选择,避免线程切换的开销。
- 如果任务需要多个并行执行单元共享数据(如 GUI、游戏开发),多线程是一个合适的方案,但要注意线程同步问题。
- 在高并发场景下,可以使用 “多进程 + 协程” 结合的方式,如 Nginx、FastAPI、Node.js 采用的模式。
4. 进程、线程、协程对比总结
对比项 | 进程 | 线程 | 协程 |
---|---|---|---|
调度方式 | 操作系统 | 操作系统 | 用户态(手动切换) |
资源占用 | 高(独立内存) | 低(共享进程内存) | 极低(仅栈和寄存器) |
通信方式 | IPC(管道、消息队列) | 共享变量(需加锁) | 共享变量(无锁) |
创建开销 | 高 | 中 | 低 |
上下文切换 | 慢 | 较快 | 极快 |
是否并行 | 是(多进程可用多核 ) | 取决于语言(Java 可以,Python 受 GIL 限制) | 否(单线程内运行) |
适用场景 | 计算密集型、多核并行 | IO 密集型、需要共享数据 | 高并发 |
__END__