下图是列表 / 元组 / 字典(list / tuple / dict)、容器(container)、可迭代对象(iterable)、迭代器(iterator)、生成器(generator)等之间的关系。
图片来源:【Python迭代器和生成器详解】
参考文章:
【python中生成器与迭代器到底有什么区别?一文带你彻底搞清楚】
【Python3 迭代器与生成器 | 菜鸟教程】
【Python生成器和迭代器的区别】
在 Python 中,可迭代对象(Iterable)是指可以一次返回其成员的对象,这意味着这些对象可以通过 for…in… 循环进行遍历。所有的序列类型,如列表(list)、字符串(str)和元组(tuple),以及一些非序列类型,如字典(dict)和文件对象,都是可迭代的。此外,任何定义了 _iter_() 方法的自定义对象也可以成为可迭代对象。
如何判断一个对象是否可以迭代?传入 collections.abc.Iterable 并利用 isinstance() 函数,该函数用于判断一个对象是否是一个已知的类型,类似于 type() 。
isinstance() 与 type() 区别为:
type() 不会认为子类是一种父类类型,不考虑继承关系。
isinstance() 会认为子类是一种父类类型,考虑继承关系。
如果要判断两个类型是否相同推荐使用 isinstance() 函数。
Python 代码示例:
from collections.abc import Iterable
class MyList(object):
def __init__(self):
self.data = []
def __iter__(self):
pass
if __name__ == '__main__':
print(isinstance([], Iterable)) # True
print(isinstance({}, Iterable)) # True
print(isinstance('abc', Iterable)) # True
print(isinstance(100, Iterable)) # False
my_list = MyList()
print(isinstance(my_list, Iterable)) # True
可以通过 iter() 函数获取可迭代对象的迭代器(Iterator),每个迭代器都是可迭代对象。通过重复调用迭代器的 next() 方法(或将其传递给内置函数 next() )可以获取下一条数据。当没有更多数据可用时,会引发 StopIteration 异常,此时,迭代器对象被耗尽,任何进一步调用其 next() 方法都将再次引发 StopIteration 异常。
Python 代码示例:
from collections.abc import Iterable
from collections.abc import Iterator
print(isinstance([], Iterable)) # True
print(isinstance([], Iterator)) # False
print(isinstance(iter([]), Iterable)) # True
print(isinstance('abc', Iterator)) # False
print(isinstance(iter('abc'), Iterator)) # True
Python 代码示例:
from collections.abc import Iterable
from collections.abc import Iterator
class MyIterator(object):
def __init__(self, my_list):
self.my_list = my_list
self.current_index = 0
def __iter__(self):
return self
def __next__(self):
if self.current_index < len(self.my_list.items):
item = self.my_list.items[self.current_index]
self.current_index += 1
return item
else:
raise StopIteration
class MyList(object):
def __init__(self):
self.items = []
def add(self, item):
self.items.append(item)
def __iter__(self):
return MyIterator(self)
if __name__ == '__main__':
mylist = MyList()
print(f'是否为迭代对象:{isinstance(mylist, Iterable)}') # True
print(f'是否为迭代器:{isinstance(mylist, Iterator)}') # False
mylist.add(1)
mylist.add(2)
mylist.add(3)
for i in mylist:
print(i, end=' ') # 1 2 3
print()
# ----------------------------------------
my_iterator = MyIterator(mylist)
print(f'是否为迭代对象:{isinstance(my_iterator, Iterable)}') # True
print(f'是否为迭代器:{isinstance(my_iterator, Iterator)}') # True
for j in my_iterator:
print(j, end=' ') # 1 2 3
for item in Iterable 循环的本质就是先通过 iter() 函数获取可迭代对象 Iterable 的迭代器 Iterator ,然后对获取到的迭代器不断调用 next() 方法来依次获取对象中的每一个数据并将其赋值给 item ,当遇到 StopIteration 的异常后循环结束。
总结:
可迭代对象(Iterable)是实现了 _iter_() 方法的对象;迭代器(Iterator)是实现了 _iter_() 方法和 _next()_ 方法的对象。
通过调用 iter() 方法可以获得一个迭代器(Iterator)。
for…in… 的迭代实际是将可迭代对象转换成迭代器,再重复调用 next() 方法实现。
Python 代码示例:
class FibIterator(object): # 斐波那契数列迭代器
def __init__(self, n):
# n 指明生成数列的前 n 个数
self.n = n
# current 用来保存当前生成到数列中的第几个数了
self.current = 0
# num1 用来保存前前一个数,初始值为数列中的第一个数 0
self.num1 = 0
# num2 用来保存前一个数,初始值为数列中的第二个数 1
self.num2 = 1
def __next__(self): # 调用 next() 函数获取下一个数
if self.current < self.n:
result = self.num1
self.num1, self.num2 = self.num2, self.num1 + self.num2
self.current += 1
return result
else:
raise StopIteration
def __iter__(self): # 迭代器的 __iter__ 返回自身即可
return self
if __name__ == '__main__':
# ---------- for 循环接收可迭代对象 ----------
fib = FibIterator(10)
for num in fib:
print(num, end=" ")
print() # 0 1 1 2 3 5 8 13 21 34
# ---------- next() 函数接收可迭代对象 ----------
f_n = FibIterator(10)
while True:
try:
n = next(f_n)
print(n, end=" ")
except StopIteration:
break
print() # 0 1 1 2 3 5 8 13 21 34
# ---------- list() tuple() 接收可迭代对象 ----------
li = list(FibIterator(8))
print(li) # [0, 1, 1, 2, 3, 5, 8, 13]
tp = tuple(FibIterator(8))
print(tp) # (0, 1, 1, 2, 3, 5, 8, 13)
生成器(generator)是一种特殊的迭代器,它是一个包含 yield 表达式的函数,用于在 for 循环中产生一系列值,或者可以一次一个地使用 next() 函数检索。
生成器通常指的是生成器函数,但在某些上下文中也可能指生成器迭代器。
创建列表生成式和生成器的区别仅在于最外层的 [ ] 和 ( ) ,可以按照迭代器的方法打印生成器中的每个元素,即可以通过 next() 函数、for 循环、list() 函数、tuple() 函数等方法。
Python 代码示例:
A = (x*2 for x in range(10))
print(type(A)) #
while True:
try:
a = next(A)
print(a, end=' ') # 0 2 4 6 8 10 12 14 16 18
except StopIteration:
break
只要在 def 中有 yield 关键字就称为生成器。yield 关键字的作用是返回一个值,并暂停函数的执行,下一次调用函数时从上一次暂停的地方继续执行。
Python 代码示例:
def fib(n):
num1, num2 = 0, 1
current = 0
while current < n:
num = num1
num1, num2 = num2, num1 + num2
current += 1
yield num
return 'done'
if __name__ == '__main__':
F = fib(10)
while True:
try:
a = next(F)
print(a, end=' ') # 0 1 1 2 3 5 8 13 21 34 done
except StopIteration as e:
print(e.value)
break
使用了 yield 关键字的函数不再是函数,而是生成器。
yield 关键字有两点作用:
使用 next() 函数让生成器从断点处继续执行,即唤醒生成器(函数)
Python3 中的生成器可以使用 return 返回最终运行的返回值。
除了可以使用 next() 函数唤醒生成器并继续执行外,还可以使用 send() 函数来唤醒执行。使用 send() 函数的一个好处是:可以在唤醒的同时向断点处传入一个附加数据,即 next() 等价于 send(None) 。
Python 代码示例:
def add_one(n):
i = 0
while i < n:
result = yield i
print(result)
# yield i
i += 1
if __name__ == '__main__':
a = add_one(5)
print(next(a)) # 0
print('-' * 10)
print(a.send('hello')) # hello 1
print('-' * 10)
print(next(a)) # None 2 ,等价于 print(a.send(None))
特性 | 可迭代对象 Iterable | 迭代器 Iterator | 生成器 Generator |
---|---|---|---|
定义 | 实现了 __iter__() 方法,能够返回一个迭代器 |
实现了 __iter__() 和 __next__() 方法的对象 |
特殊的迭代器,由生成器函数或生成器表达式创建 |
作用 | 提供一个接口可用于遍历元素 | 用于遍历元素,生成序列中的下一个元素 | 生成一个值序列的迭代器,支持惰性计算 |
方法 | __iter__() 返回迭代器 |
__iter__() 返回自身,__next__() 返回下一个元素 |
继承自迭代器,实现 __iter__() 和 __next__() |
使用方式 | 可直接用于 for 循环 |
通常不直接用for ,需手动调用next() 获取值 |
可直接用于 for 循环或手动调用 next() |
关系 | 可生成迭代器,迭代器是它的 “产物” | 是可迭代对象的具体遍历器 | 是一种特殊的迭代器,由生成器函数生成 |
是否有状态 | 通常无状态,持有集合或容器 | 有状态,跟踪当前元素的位置 | 有状态,且可以在暂停处保存上下文和变量状态 |
生成方式 | 任何实现了 __iter__() 的对象,如 list、tuple |
由可迭代对象的 __iter__() 创建 |
使用 yield 关键字的函数 |
优点 | 简单,可遍历多种容器对象 | 支持遍历所有元素,避免一次性加载全部数据 | 支持惰性生成,节省内存,生成复杂序列更灵活 |
示例 | list、str、dict | list 的 iter(list) 返回的对象 |
def gen(): yield 1 创建的生成器对象 |
协程是一种轻量级的并发执行方式,允许程序在执行过程中暂停并恢复,而无需操作系统介入。
相比于进程和线程,协程避免了内核态和用户态的切换开销,提高了执行效率。
协程在遇到 I/O 阻塞时不会阻塞整个进程,且不需要加锁机制,简化了同步问题。
协程是一种用户级的轻量级线程,拥有自己的寄存器上下文和栈,调度切换时保存和恢复寄存器上下文和栈。
协程允许执行被挂起与被恢复。
在单线程里,多个函数可以并发地执行,这就是协程。
在多 CPU 核数下多进程、多线程可能是并行执行的,但是协程只存在在一个线程中, 因此协程切换时任务资源很小,效率高,且只能是并发执行。
Python 代码示例:
import time
def work1():
while True:
print("----work1---")
yield
time.sleep(0.5)
def work2():
while True:
print("----work2---")
yield
time.sleep(0.5)
def main():
w1 = work1()
w2 = work2()
while True:
next(w1)
next(w2)
if __name__ == "__main__":
main()
运行结果:
----work1---
----work2---
----work1---
----work2---
----work1---
----work2---
… …
greenlet 是一个底层工具,适合需要手动管理协程切换的场景。
pip 安装包网址:【pip 25.1.1】
在解压文件的目录下进入命令提示符 cmd ,输入 python setup.py install
命令进行安装,安装成功后重启电脑。
在命令提示符 cmd 上输入 pip list
命令,测试 pip 是否安装成功。
快捷键 Win + R ,输入 %APPDATA% 并回车,进入 “C:\Users\[用户名]\AppData\Roaming” 目录下,在 pip 文件夹下(没有则创建)新建 pip.ini 文件。
以记事本方式打开 pip.ini 文件,编辑以下内容并保存:
[global]
timeout = 6000
index-url = https://pypi.tuna.tsinghua.edu.cn/simple
trusted-host = pypi.tuna.tsinghua.edu.cn
C:\Users\[用户名]\AppData\Roaming\pip\pip.ini
新建至 PATH 路径下。注:升级 pip 的命令为
python -m pip install --upgrade pip
。
打开 cmd ,输入 pip install greenlet
命令并回车。
输入 pip list|grep greenlet
命令验证 greenlet 是否安装成功。
greenlet 模块的常用方法和属性:
方法 | 介绍 |
---|---|
switch() |
切换到指定的 greenlet 并执行其代码 |
getcurrent() |
返回当前正在运行的 greenlet 对象 |
属性 | 介绍 |
---|---|
parent |
表示当前 greenlet 的父级 greenlet(通常是创建它的 greenlet) |
dead |
检查 greenlet 是否已经完成执行 |
import time
from greenlet import greenlet
def work1():
while True:
print("----work1---")
gr2.switch()
time.sleep(0.5)
def work2():
while True:
print("----work2---")
gr1.switch()
time.sleep(0.5)
gr1 = greenlet(work1)
gr2 = greenlet(work2)
# 切换到 gr1 中运行
gr1.switch()
运行结果:
----work1---
----work2---
----work1---
----work2---
----work1---
----work2---
… …
gevent 是一个基于 greenlet 实现的网络库,通过 greenlet 实现协程。
gevent 模块的原理是:当 greenlet 遇到 I/O(指 input output 输入输出,比如网络、文件操作等)操作时,比如访问网络阻塞,就自动切换到其他的协程,等到 I/O 操作完成,再在适当的时候切换回来继续执行。
由于 I/O 操作非常耗时,经常使程序处于等待状态,有了 gevent 模块自动切换协程,就保证总有协程在运行,而不是在等待 I/O 。
打开 cmd ,输入 pip install gevent
命令并回车。
输入 pip list|grep gevent
命令验证 gevent 是否安装成功。
gevent 模块的常用方法:
方法 | 介绍 |
---|---|
gevent.spawn() |
创建一个普通的 greenlet 对象并切换 |
gevent.spawn_later(seconds=3) |
延时创建一个普通的 greenlet 对象并切换 |
gevent.spawn_raw() |
创建的协程对象属于一个组 |
gevent.getcurrent() |
返回当前正在执行的 greenlet |
gevent.joinall(jobs) |
将协程任务添加到事件循环,接收一个任务列表 |
gevent.wait() |
可以替代 join 函数等待循环结束,也可以传入协程对象列表 |
gevent.kill() |
杀死一个协程 |
gevent.killall() |
杀死一个协程列表里的所有协程 |
monkey.patch_all() |
自动将 python 的一些标准模块替换成 gevent 框架 |
import gevent
def run(n):
for i in range(n):
print(f'协程 : {gevent.getcurrent()} 执行任务 : {i}')
# 用来模拟一个耗时操作,注意不是 time 模块中的 sleep
gevent.sleep(1)
gr1 = gevent.spawn(run, 3)
gr2 = gevent.spawn(run, 3)
gr3 = gevent.spawn(run, 3)
gr1.join()
gr2.join()
gr3.join()
运行结果:
协程 : <Greenlet at 0x19a46eba200: run(3)> 执行任务 : 0
协程 : <Greenlet at 0x19a7250a3e0: run(3)> 执行任务 : 0
协程 : <Greenlet at 0x19a7250a2a0: run(3)> 执行任务 : 0
协程 : <Greenlet at 0x19a46eba200: run(3)> 执行任务 : 1
协程 : <Greenlet at 0x19a7250a3e0: run(3)> 执行任务 : 1
协程 : <Greenlet at 0x19a7250a2a0: run(3)> 执行任务 : 1
协程 : <Greenlet at 0x19a46eba200: run(3)> 执行任务 : 2
协程 : <Greenlet at 0x19a7250a3e0: run(3)> 执行任务 : 2
协程 : <Greenlet at 0x19a7250a2a0: run(3)> 执行任务 : 2
猴子补丁的作用是:在程序运行时动态替换某些功能,通常在程序启动时进行。举个例子,如果一个游戏服务器的代码里有多处写了 import json
,后来发现使用 ujson 这个库比自带的 json 快好多倍,那么需要去每个文件中把 import json
改成 import ujson as json
吗?其实不用,只要在程序启动时用猴子补丁替换一次即可,这样整个进程里所有的 json 调用都会变成用 ujson 。
import json
import ujson
def monkey_patch_json():
json.__name__ = 'ujson'
json.dumps = ujson.dumps
json.loads = ujson.loads
monkey_patch_json()
再比如用协程访问多个网站的情况,因为网络 I/O 操作很耗时,程序会经常处于等待状态。用 gevent 时,只要在启动时执行 gevent.monkey.patch_all()
,它会自动帮我们处理阻塞,遇到等待时自动切换协程,这样程序运行就更高效了。
Python 代码示例:
from gevent import monkey
import gevent
import random
import time
# 有耗时操作时需要,将程序中用到的耗时操作的代码,换为 gevent 中自己实现的模块
monkey.patch_all()
def coroutine_work(coroutine_name):
for i in range(3):
print(f'协程 : {coroutine_name} 执行任务 : {i}')
time.sleep(random.random())
gevent.joinall([gevent.spawn(coroutine_work, "work1"), gevent.spawn(coroutine_work, "work2")])
运行结果:
协程 : work1 执行任务 : 0
协程 : work2 执行任务 : 0
协程 : work2 执行任务 : 1
协程 : work1 执行任务 : 1
协程 : work2 执行任务 : 2
协程 : work1 执行任务 : 2
在 Python 3.12 中,asyncio 包的性能得到了多项改进,其中一些基准测试显示有 75% 的提速。这些改进包括:
改进的事件循环性能:事件循环的性能优化使得任务调度和 I/O 操作更加高效。
优化的任务切换:减少了上下文切换的开销,提升了任务执行效率。
更快的 I/O 操作:对底层 I/O 操作进行了优化,提升了整体吞吐量。
增强的 asyncio.run
:在处理大量并发任务时,asyncio.run 的性能得到了改进。
这些改进使得使用 asyncio 进行异步编程在 Python 3.12 中更加高效,适合处理大量并发的 I/O 绑定任务。
# 示例:使用 asyncio 的异步 I/O 操作
import asyncio
import random
async def fetch_data(delay, name):
"""
async def fetch_data(delay, name):定义一个异步函数,该函数模拟一个耗时操作。
await asyncio.sleep(delay):模拟延迟操作,使当前协程暂停 delay 秒,而不阻塞事件循环。这使得其他协程可以在这段时间内运行。
print 和 return:分别输出任务开始和结束的信息,并返回任务的结果。
"""
print(f'Starting fetch for {name} with a delay of {delay} seconds...')
await asyncio.sleep(delay)
print(f'Finished fetch for {name}')
return f'Data from {name}'
async def main():
# 定义多个异步任务
tasks = []
for i in range(1, 4):
tasks.append(fetch_data(random.randint(1, 5), f'Task{i}'))
# 并发执行所有任务,并等待它们完成,gather()方法返回一个列表
results = await asyncio.gather(*tasks)
# 打印结果
print(results)
# 在 Python 3.12 中直接运行异步主函数
# Python 3.12 asyncio 包的性能获得了多项改进,一些基准测试显示有 75% 的提速
if __name__ == '__main__':
asyncio.run(main()) # 3.12 及以上版本
运行结果:
Starting fetch for Task1 with a delay of 1 seconds...
Starting fetch for Task2 with a delay of 4 seconds...
Starting fetch for Task3 with a delay of 3 seconds...
Finished fetch for Task1
Finished fetch for Task3
Finished fetch for Task2
['Data from Task1', 'Data from Task2', 'Data from Task3']
greenlet
:轻量,适合做底层协程调度,但开发复杂,适合高级用户用来自定义协程模型。asyncio 和 gevent 都是用于异步编程的库,但它们在设计和实现上有一些显著的不同,这影响了它们在不同场景中的效率和适用性。
gevent
:基于 greenlet ,增强了网络 I/O 的支持和自动协程调度,适合网络高并发场景,封装成熟。
asyncio
:Python 标准异步框架,语法现代且有广泛生态支持,适合新项目和未来发展,学习曲线相对较陡。
特性 | greenlet | gevent | asyncio |
---|---|---|---|
定义和定位 | 轻量级协程库,实现协程的切换(微线程) | 基于 greenlet 的协程库,提供事件驱动网络编程,实现协程自动切换 | Python 标准库原生异步框架,基于事件循环和协程编程 |
核心实现原理 | 协程切换,由开发者手动切换(协程的上下文切换) | 事件循环 + 协程自动切换,内置大量网络 I/O 非阻塞操作封装 | 事件循环,任务调度,基于 async /await 语法 |
编程模型 | 手动管理切换 | 自动调度,写法简单,类似同步编程 | 异步 / await 语法,原生支持异步函数 |
I/O 模型 | 不提供原生非阻塞 I/O ,需要配合其他库实现 | 内置非阻塞网络和 I/O 库,自动切换协程 | 事件驱动非阻塞 I/O ,依赖异步 I/O 库 |
生态与依赖 | 体积小,无额外依赖 | 依赖 greenlet ,生态丰富,广泛用于网络服务器 | Python 标准库,依赖底层异步支持(如 Proactor、Selector 事件循环) |
易用性 | 低,需手动切换,使用门槛较高 | 高,封装良好,接近同步模型,易读易维护 | 中等,需要理解 async / await 及事件循环机制 |
性能 | 切换开销极低,且切换灵活 | 较高效,适合大量并发 I/O 任务,切换快速 | 性能良好,标准库支持,适合大规模异步任务 |
主应用场景 | 需要细粒度协程调度的场景 | 网络并发服务器,如爬虫、聊天服务器等 | Web 服务、异步数据库操作、异步 HTTP 客户端等 |
优点 | 轻量、无依赖、切换快速,适合底层协程库 | 生态丰富,支持多种网络协议库,使用方便,支持猴子补丁协程化第三方库 | 标准库支持,语法现代化,方便集成,适合异步任务和现代 Python async 生态 |
缺点 | 编程模型复杂,需手动管理切换,缺乏高级封装 | 依赖 greenlet ,猴子补丁可能带来兼容问题 | 学习曲线较陡,运行时调试较复杂,部分同步库不兼容异步环境 |
性能对比:
情况 | greenlet | gevent | asyncio |
---|---|---|---|
上下文切换开销 | 最小(极快) | 较小 | 较小 |
并发处理 I/O | 低(需手动处理) | 高(自动协程切换,非阻塞 I/O) | 高(事件循环和原生异步 I/O) |
CPU 密集任务 | 无特殊优势 | 无特殊优势 | 无特殊优势 |
兼容性 | 高(裸协程切换层) | 有时会遇到第三方库兼容问题(猴子补丁) | 与 Python 生态兼容最好 |
结论:
如果需要与其他现代 Python 异步库和框架进行集成,或者希望使用标准库并获得跨平台支持,asyncio
是一个更好的选择。
如果需要处理大量小型 I/O 任务,并且希望简化代码编写,gevent
可能会更高效。
# asyncio 示例
import asyncio
import aiohttp # pip install aiohttp
async def fetch(session, url):
async with session.get(url) as response:
return await response.text()
async def main():
async with aiohttp.ClientSession() as session:
tasks = [fetch(session, 'http://baidu.com') for _ in range(4)]
results = await asyncio.gather(*tasks)
for result in results:
print(result)
if __name__ == '__main__':
asyncio.run(main())
# gevent 示例
import gevent
from gevent import monkey
monkey.patch_all()
import requests
import time
def fetch(url):
response = requests.get(url)
time.sleep(0.5)
return response.text
def main():
urls = ['http://baidu.com' for _ in range(4)]
jobs = [gevent.spawn(fetch, url) for url in urls]
gevent.joinall(jobs)
for job in jobs:
print(job.value)
if __name__ == '__main__':
main()
运行结果:
问题:在运行 gevent 模块的 Python 代码示例时,出现 “urllib3.exceptions.ProtocolError: (‘Connection aborted.’, ConnectionResetError(10054, ‘远程主机强迫关闭了一个现有的连接。’, None, 10054, None))” 的错误怎么办?
原因:没有 sleep 几秒,从而在短时间内对网站大量使用 requests 操作,导致网站认定是攻击行为,因此抛出异常。
解决方法:在头部引入 import time
,并在调用 requests 的循环内,加入方法 time.sleep(0.5)
即可解决。
from gevent import monkey
import gevent
# 有耗时操作时需要
monkey.patch_all()
import requests # cmd 下 pip install requests
import time
def my_download(url):
print('GET: <%s> .' % url)
response = requests.get(url)
data = response.text
print('%d bytes received from <%s> .' % (len(data), url))
# 使用协程
start = time.time()
gevent.joinall([
gevent.spawn(my_download, 'http://www.baidu.com/'),
gevent.spawn(my_download, 'http://www.163.com/'),
gevent.spawn(my_download, 'http://www.runoob.com/'),
gevent.spawn(my_download, 'http://www.51cto.com/'),
gevent.spawn(my_download, 'http://www.cnblogs.com/')
])
end = time.time()
print('The total time spent using coroutines: (%f) .' % (end - start))
print('-' * 50)
# 不使用协程
start = time.time()
my_download('http://www.baidu.com/')
my_download('http://www.163.com/')
my_download('http://www.runoob.com/')
my_download('http://www.51cto.com/')
my_download('http://www.cnblogs.com/')
end = time.time()
print('The total time spent without using coroutines: (%f) .' % (end - start))
运行结果展示:
由上图可以看出:使用协程时收到数据的先后顺序不一定与发送顺序相同,这体现出了异步,即不确定什么时候会收到数据。
运行时出现 “MonkeyPatchWarning: Monkey-patching ssl after ssl has already been imported may lead to errors, including RecursionError on Python 3.6. It may also silently lead to incorrect behaviour on Python 3.7. Please monkey-patch earlier. See …… Modules that had direct imports (NOT patched): ……” 的警告怎么办?
解决方法:将下面三行代码写在所有引入语句的最前面,即可解决。
import gevent
from gevent import monkey
monkey.patch_all()
from gevent import monkey
import gevent
# 有耗时操作时需要
monkey.patch_all()
import requests # cmd 下 pip install requests
def my_download(file_name, url):
print('GET: <%s> .' % url)
response = requests.get(url)
data = response.content
with open(file_name, "wb") as f:
f.write(data)
print('%d bytes received from <%s> .' % (len(data), url))
gevent.joinall([
gevent.spawn(my_download, "picture1.jpg",
'http://qzs.qq.com/qzone/v6/v6_config/upload/7a082c0dde36eac2205a088397aaf295.jpg'),
gevent.spawn(my_download, "picture2.jpg",
'https://picb4.photophoto.cn/25/832/25832894_1.jpg'),
gevent.spawn(my_download, "picture3.jpg",
'https://cdn.pixabay.com/photo/2025/05/30/03/00/border-collie-9630551_1280.jpg')
])
运行结果展示:
特性 | 进程 Process | 线程 Thread | 协程 Coroutine |
---|---|---|---|
定义 | 操作系统管理的资源分离的执行单元 | 进程内的轻量级执行单元,共享进程资源 | 程序内部调度的轻量级 “用户级线程” |
资源隔离 | 独立内存空间,资源完全隔离 | 共享进程内存资源 | 共享进程内存,由程序调度 |
创建开销 | 大,涉及系统调用及资源分配 | 较小,系统线程上下文切换开销中等 | 极小,协程切换在用户态完成 |
切换开销 | 高,操作系统切换上下文 | 中,操作系统线程切换 | 低,协程切换几乎无系统调用 |
并发类型 | 真并行(多核),适合 CPU 密集型 | 伪并行(受 GIL 限制,CPython 中只有一线程执行 Python 字节码) | 单线程内的并发,非抢占式切换 |
适用场景 | CPU 密集型任务,进程间隔离高的任务 | I/O 密集型、多线程任务但 CPU 利用有限 | 大量 I/O 密集且逻辑清晰的异步编程 |
编程复杂度 | 相对复杂,进程间通信(IPC)较麻烦 | 复杂,竞态条件和锁机制需注意 | 简单,代码结构清晰,顺序风格异步 |
线程安全 | 不存在线程安全问题,但需进程间通信同步 | 存在线程安全问题,需同步机制 | 无需锁,多数情况下是顺序执行 |
内存占用 | 多,独立内存空间 | 少,共享内存 | 极少,协程是函数级别调度 |
调试难度 | 较高 | 高 | 较低 |
Python GIL 影响 | 无影响,多个进程不共享 GIL | 影响较大,不能利用多核 CPU | 不存在 GIL 问题,单线程运行,但异步并发 |
操作系统依赖 | 强,依赖底层操作系统 | 依赖操作系统线程模型 | 纯用户态实现,无需操作系统支持 |
示例库 | multiprocessing |
threading |
asyncio , greenlet , gevent |
优缺点总结:
类型 | 优点 | 缺点 |
---|---|---|
进程 | 真正多核并行,资源隔离好,稳定性高 | 启动慢、切换开销大,进程间通信复杂 |
线程 | 启动快,系统支持,适合 I/O 密集型 | 受 GIL 限制,线程安全复杂,可能出现死锁 |
协程 | 轻量级,切换快,无 GIL 限制,适合高并发 I/O | 只能运行在单线程内,CPU 密集任务效果差,需要异步编程 |
适用选择建议:
CPU 密集型:优先选多进程,避免 GIL 瓶颈。
I/O 密集型:多线程或协程,协程效率更高且代码结构优雅。
高并发网络服务:推荐使用协程(如 asyncio
、gevent
)。
复杂数据隔离或安全要求高的场景:选择多进程。