python协程

前天写了一篇博客整理了一下 python 多进程 和 多线程 的一些概念和优缺点特效,后来被一个同事问到什么是python 协程,回答不出一二,然后被疯狂嘲讽了一番 😅 后来去网上查阅了很多资料,看了很多大神i写的博文,学习到很多,在这里我记录一下。

协程

概念

  协程,又称微线程,纤程,英文名Coroutine。协程的作用,是在执行函数A时,可以随时中断,去执行函数B,然后中断继续执行函数A(可以自由切换)。但这一过程并不是函数调用(没有调用语句),这一整个过程看似像多线程,然而协程只有一个线程执行。   

优势

  • 执行效率极高,因为子程序切换(函数)不是线程切换,由程序自身控制,没有切换线程的开销。所以与多线程相比,线程的数量越多,协程性能的优势越明显。
  • 不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在控制共享资源时也不需要加锁,因此执行效率高很多。

    说明:协程可以处理IO密集型程序的效率问题,但是处理CPU密集型不是它的长处,如要充分发挥CPU利用率可以结合多进程+协程。

Pyrhon 2.x 协程

Gevent

gevent是第三方库,通过greenlet实现协程,其基本思想:
当一个greenlet遇到IO操作时,比如访问网络,就自动切换到其他的greenlet,等到IO操作完成,再在适当的时候切换回来继续执行。由于IO操作非常耗时,经常使程序处于等待状态,有了gevent为我们自动切换协程,就保证总有greenlet在运行,而不是等待IO。

我们先来看看一个简单的爬虫例子,在python3 的环境运行的:

1
2
3
4
5
6
7
8
9
10
from urllib.request import urlopen
import gevent
from gevent import monkey;monkey.patch_all()

def get_body(i):
print("start",i)
urlopen("http://cn.bing.com")
print("end",i)
tasks=[gevent.spawn(get_body,i) for i in range(3)]
gevent.joinall(tasks)

运行结果:

1
2
3
4
5
6
start 0
start 1
start 2
end 0
end 2
end 1

  说明:从执行结果上来看,执行get_body的顺序应该先是输出”start”,然后执行到 urlopen 时碰到IO堵塞,则会自动切换运行下一个程序(继续执行get_body输出start),直到 urlopen 返回结果,再执行end。也就是说,程序没有等待 urlopen 请求网站返回结果,而是直接先跳过了,等待执行完毕再回来获取返回值。值得一提的是,在此过程中,只有一个线程在执行,因此这与多线程的概念是不一样的。         


换成多线程的代码看看:

1
2
3
4
5
6
7
8
9
10
11
12
13
import threading
from urllib.request import urlopen


def get_body(i):
print("start",i)
urlopen("http://cn.bing.com")
print("end",i)


for i in range(3):
t = threading.Thread(target=get_body,args=(i,))
t.start()

执行结果:

1
2
3
4
5
6
start 0
start 1
start 2
end 1
end 0
end 2

说明:从结果来看,多线程与协程的效果一样,都是达到了IO阻塞时切换的功能。不同的是,多线程切换的是线程(线程间切换),协程切换的是上下文(可以理解为执行的函数)。而切换线程的开销明显是要大于切换上下文的开销,因此当线程越多,协程的效率就越比多线程的高。(猜想多进程的切换开销应该是最大的)

Gevent使用说明

  • monkey可以使一些阻塞的模块变得不阻塞,机制:遇到IO操作则自动切换,手动切换可以用gevent.sleep(0)(将爬虫代码换成这个,效果一样可以达到切换上下文)

  • gevent.spawn 启动协程,参数为函数名称,参数名称

  • gevent.joinall 停止协程

asynico/await

为了简化并更好地标识异步IO,从Python 3.5开始引入了新的语法async和await,可以让coroutine的代码更简洁易读。

ls:

1
2
3
4
5
6
7
8
9
>>> import asyncio
>>> async def test(i):
... print("test_1",i)
... await asyncio.sleep(1)
... print("test_2",i)
...
>>> loop = asyncio.get_event_loop()
>>> tasks=[test(i) for i in range(5)]
>>> loop.run_until_complete(asyncio.wait(tasks))

输出结果

1
2
3
4
5
6
7
8
9
10
11
test_1 2
test_1 0
test_1 3
test_1 4
test_1 1
test_2 2
test_2 0
test_2 3
test_2 4
test_2 1
({<Task finished coro=<test() done, defined at <stdin>:1> result=None>, <Task finished coro=<test() done, defined at <stdin>:1> result=None>, <Task finished coro=<test() done, defined at <stdin>:1> result=None>, <Task finished coro=<test() done, defined at <stdin>:1> result=None>, <Task finished coro=<test() done, defined at <stdin>:1> result=None>}, set())
-------------本文结束感谢您的阅读-------------
坚持原创技术分享,您的支持将鼓励我继续创作!