协程, 不是线程, 也不是进程, 它也能实现多任务
但从技术本身来说, 它是”单进程, 单线程”的, 它是一种巧妙的函数封装技术
例如, 有一个奇数列表, 不断的取值是 1, 3, 5, 7… , 有一个偶数列表, 不断取值是 2, 4, 6, 8… 那么想要同时取值, 可以使用多线程或多进程来实现, 而协程也可以让它们看起来是”多任务”, 即取一个奇数, 取一个偶数
手动实现协程
协程, 并非操作系统实现的, 而是编程人员用代码实现的, 从操作系统上看并非多任务, 单从用户角度是多任务, 例如以下代码
gl_num1 = [1, 3, 5]
gl_num2 = [2, 4, 6]
current_a = 0
current_b = 0
def next_a():
try:
global current_a
num1 = gl_num1[current_a]
current_a += 1
return num1
except Exception as ex:
return -1
def next_b():
try:
global current_b
num2 = gl_num2[current_b]
current_b += 1
return num2
except Exception as ex:
return -1
def coroutine_a():
"""取a列表"""
while True:
num1 = next_a()
if num1 == -1:
break
print(num1)
def coroutine_b():
"""取b列表"""
while True:
num2 = next_b()
if num2 == -1:
break
print(num2)
def coroutine():
"""取一个a, 取一个b"""
while True:
a = next_a()
if a == -1:
break
print(a)
b = next_b()
if b == -1:
break
print(b)
def main():
# coroutine_a() # 1, 3, 5
# coroutine_b() # 2, 4, 6
coroutine() # 1, 2, 3, 4, 5, 6
if __name__ == "__main__":
main()
协程比线程, 进程消耗的更少, 在某些情况下是一个好选择
yield实现协程
显然, 这样的封装太依赖于技巧, 而python中, 通过前面学习的yield可以轻松实现
import time
def task_1():
while True:
print("---1---")
time.sleep(1)
yield
def task_2():
while True:
print("---2---")
time.sleep(1)
yield
def main():
t1 = task_1()
t2 = task_2()
while True:
next(t1)
next(t2)
if __name__ == "__main__":
main()
greenlet实现协程
为了更好的使用协程来完成多任务, python中的greenlet模块对其封装, 从而使得任务切换更加简单
安装模块
pip install greenlet
greenlet实例
from greenlet import greenlet
import time
def test1():
while True:
print("----A----")
gr2.switch()
time.sleep(1)
def test2():
while True:
print("----B----")
gr1.switch()
time.sleep(1)
gr1 = greenlet(test1)
gr2 = greenlet(test2)
# 开始执行, 切换到任务1
gr1.switch()
执行结果:
----A----
----B----
----A----
----B----
----A----
----B----
----A----
...
gevent实现协程
gevent是比greenlet更简单的协程实现方式
该模块同样需要安装, pip install gevent
简单案例
gevent实现协程的核心是, 利用sleep的时间, 去做其他事情, gevent中只有sleep才能实现多任务, 必须使用gevent.sleep()
import gevent
def f1(n):
for i in range(n):
print(gevent.getcurrent(), i)
gevent.sleep(0.5) # sleep多久, 就是延迟多久, 只有sleep才会去做别的
def f2(n):
for i in range(n):
print(gevent.getcurrent(), i)
gevent.sleep(0.5)
def f3(n):
for i in range(n):
print(gevent.getcurrent(), i)
gevent.sleep(0.5)
g1 = gevent.spawn(f1, 5) # 指定任务, 及参数
g2 = gevent.spawn(f2, 5)
g3 = gevent.spawn(f3, 5)
g1.join() # 开始任务
g2.join()
g3.join()
程序执行结果:
<Greenlet at 0x193ee17f048: f1(5)> 0
<Greenlet at 0x193ee17f390: f2(5)> 0
<Greenlet at 0x193ee17f4a8: f3(5)> 0
<Greenlet at 0x193ee17f048: f1(5)> 1
<Greenlet at 0x193ee17f390: f2(5)> 1
<Greenlet at 0x193ee17f4a8: f3(5)> 1
<Greenlet at 0x193ee17f048: f1(5)> 2
<Greenlet at 0x193ee17f390: f2(5)> 2
<Greenlet at 0x193ee17f4a8: f3(5)> 2
<Greenlet at 0x193ee17f048: f1(5)> 3
<Greenlet at 0x193ee17f390: f2(5)> 3
<Greenlet at 0x193ee17f4a8: f3(5)> 3
<Greenlet at 0x193ee17f048: f1(5)> 4
<Greenlet at 0x193ee17f390: f2(5)> 4
<Greenlet at 0x193ee17f4a8: f3(5)> 4
Process finished with exit code 0
gevent.joinall() 参数是一个列表, 可以一次加入多个任务
对于现有的程序, time.sleep换成gevent.sleep并不是一个好的办法, 而是给程序”打补丁”
在打补丁模块的头部, 写入如下代码打补丁
from gevent import monkey
monkey.patch_all()
在某些下载器中, 同时下载多个任务时, 又不让服务器压力过大, 不给你多线程多进程, 而是协程方式
而在服务器多任务中中,通常每个进程就需要几mb内存,100并发则至少需要几百mb内存,若合理的使用协程做多线程,能承受巨大的并发量,因为它的消耗最少。