python协程
1、协程,又称微线程,纤程。英文名Coroutine,协程并非python专属的概念,而是一个计算机概念,不同编程语言有不同的实现。
简单协程的实现
import time
def work1():
while True:
print("----work1---")
yield 1
time.sleep(0.5)
def work2():
while True:
print("----work2---")
yield 2
time.sleep(0.5)
def main():
w1 = work1()
w2 = work2()
while True:
next(w1)
next(w2)
if __name__ == "__main__":
main()
- yield 有让步之意,因为它交出了程序的控制权,但这个协程并没有结束,下一次执行时,将恢复到之前让出程序控制权的地方,也就是yield语句执行的地方继续执行。在上面的代码里,我创建了两个生成器(姑且认为,函数里有yiled就算生成器),也就是创建了两个协程,并且让这两个协程交替执行
想象一下,work1函数中,yield 语句执行的地方不是紧跟着一个整数,而是一个IO操作,那么这就会大大提高程序的并发。IO操作是阻塞的,耗时的,但协程可以在遇到IO操作的时候将程序的控制权让出,这个时候别的协程获得程序控制权继续执行。
一般的函数具备这样的功能么?正常我们在函数中,把程序控制权交出去,其本质不就是函数运行结束么?(使用return),无论如何也不会在中间某个位置交出控制权,过一段时间又回到这个位置继续执行,而协程就可以。
上面的代码,始终是在同一个线程内执行,这是很多人都会忽视的一个关键事实。多个协程在一个线程内协作,在协程间的切换不涉及到任何系统调用,也不会产生阻塞。
为了更好使用协程来完成多任务,python中的greenlet模块对其封装,从而使得切换任务变的更加简单
from greenlet import greenlet
import time
def test1():
while True:
print("---A--")
gr2.switch()
time.sleep(0.5)
def test2():
while True:
print("---B--")
gr1.switch()
time.sleep(0.5)
gr1 = greenlet(test1)
gr2 = greenlet(test2)
#切换到gr1中运行
gr1.switch() #通过调用gr1的switch方法,我们启动了gr1协程。因为gr1内部有一个while True循环,并且会不断地切换到gr2,而gr2也会不断地切换回gr1,所以这两个协程会交替执行,从而不断地在控制台打印出"---A--"和"---B--"。
- 1、 协程与进程、线程的不同
- 进程、线程 创建完之后,到底是哪个进程、线程执行 不确定,这要让操作系统来进行计算(调度算法,例如优先级调度)
- 协程是可以人为来控制的
- 2、greenlet库提供的协程是完全由用户控制的,这意味着我们需要显式地调用switch方法来在协程之间进行切换。这与Python内置的async/await语法(用于异步编程)有所不同,后者提供了更高级别的协程控制,并且可以自动处理协程之间的切换。
import gevent
def f1(n):
for i in range(n):
print("-----f1-----", i)
def f2(n):
for i in range(n):
print("-----f2-----", i)
def f3(n):
for i in range(n):
print("-----f3-----", i)
g1 = gevent.spawn(f1, 5)
g2 = gevent.spawn(f2, 5)
g3 = gevent.spawn(f3, 5)
g1.join() # join会等待g1标识的那个任务执行完毕之后 对其进行清理工作,其实这就是一个 耗时操作
g2.join()# 等待g2标识的协程(即f2函数的执行)完成
g3.join()# 等待g3标识的协程(即f3函数的执行)完成
-
1、可以看到,3个greenlet是依次运行而不是交替运行
-
2、使用gevent来实现多任务的时候,有一个很特殊的地方它可以自行切换协程指定的任务,而且切换的前提是:当一个任务用到耗时操作(例如延时),它就会把这个时间拿出来去做另外的任务,这样做最终实现了多任务 而且自动切换,如下
import gevent
def f1(n):
for i in range(n):
print("-----f1-----", i)
gevent.sleep(1)
def f2(n):
for i in range(n):
print("-----f2-----", i)
gevent.sleep(1)
def f3(n):
for i in range(n):
print("-----f3-----", i)
gevent.sleep(1)
g1 = gevent.spawn(f1, 5)
g2 = gevent.spawn(f2, 5)
g3 = gevent.spawn(f3, 5)
g1.join() # join会等待g1标识的那个任务执行完毕之后 对其进行清理工作,其实这就是一个 耗时操作
g2.join()
g3.join()
# 使用gevent来实现多任务的时候,有一个很特殊的地方
# 它可以自行切换协程指定的任务,而且切换的前提是:当一个任务用到耗时操作(例如延时),它就会把这个时间拿出来去做另外的任务
# 这样做最终实现了多任务 而且自动切换
3、并发下载视频
1、可以将url换为自己需要下载视频、音乐、图片等网址,重新执行即可完成下载
from gevent import monkey
import gevent
import urllib.request
import ssl
monkey.patch_all()
ssl._create_default_https_context = ssl._create_unverified_context
def my_downLoad(file_name, url):
print('GET: %s' % url)
resp = urllib.request.urlopen(url)
data = resp.read()
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, "1.mp4", '视频URL地址'),
gevent.spawn(my_downLoad, "2.mp4", '视频URL地址2')
])