先来看一下yield
def producer(name):
for i in range(1,10):
print("[%s] 生产了包子[%s]" %(name, i))
yield '包子[%s]'%i
def consumer(name):
while True:
bz = yield
print("[%s]取到了[%s],并且吃了它" %(name, bz))
if __name__ == '__main__':
p = producer('Alex')
c1 = consumer('xiaoming')
# c2 = consumer('small')
c1.__next__()
# c2.__next__()
for i in p:
print('i:',i)
c1.send(i)
# c2.send(i)
线程和进程的操作是由程序触发系统接口,最后的执行者是系统;协程的操作则是程序本身。
协程存在的意义:对于多线程应用,CPU通过切片的方式来切换线程间的执行,线程切换时需要耗时(保存状态,下次继续)。协程,则只使用一个线程,在一个线程中规定某个代码块执行顺序。
协程的适用场景:当程序中存在大量不需要CPU的操作时(IO),适用于协程;
greenlet
greenlet是一个用C实现的协程模块。一句话来说明greenlet的实现原理:通过栈的复制切换来实现不同协程之间的切换
from greenlet import greenlet
def test1():
print(12) #2
gr2.switch() #3
print(34) #6
# gr1.switch()
def test2():
print(56) #4
gr1.switch() #5
print(78)
gr1 = greenlet(test1) #生成一个greenlet的对象
gr2 = greenlet(test2) #再生成一个greenlet对象
gr1.switch() #1
输出为:
12
56
34
当创建一个greenlet时,首先初始化一个空的栈, switch到这个栈的时候,会运行在greenlet构造时传入的函数(首先在test1中打印 12), 如果在这个函数(test1)中switch到其他协程(到了test2 打印34),那么该协程会被挂起,等到切换回来(在test2中切换回来 打印34)。当这个协程对应函数执行完毕,那么这个协程就变成dead状态。
注意 上面没有打印test2的最后一行输出 78,因为在test2中切换到gr1之后挂起,但是没有地方再切换回来。这个可能造成泄漏,后面细说。
gevent
Gevent 是一个第三方库,可以轻松通过gevent实现并发同步或异步编程,在gevent中用到的主要模式是Greenlet, 它是以C扩展模块形式接入Python的轻量级协程。 Greenlet全部运行在主程序操作系统进程的内部,但它们被协作式地调度。
import gevent
def test1():
print('in test1')
gevent.sleep(1)
print(111)
def test2():
print('in test2')
gevent.sleep(0)
print(222)
gevent.joinall([gevent.spawn(test1),
gevent.spawn(test2),])
print("end")
join的意义
import gevent
import time
def talk(msg):
print(msg)
gevent.sleep(0)
print(msg)
g1 = gevent.spawn(talk, 'bar')
gevent.sleep(0)
# gevent.joinall([g1,]) #加上join将输出 bar bar end
print('end')
输出:
bar
end
协程gevent简单的爬网页
from gevent import monkey
import gevent, time
from urllib import request
monkey.patch_all() #遇到io操作就做上标记
def f(url):
print('GET: %s' %url)
res = request.urlopen(url)
data = res.read()
print('%d bytes received from %s.' %(len(data), url))
start_time = time.time()
gevent.joinall([
gevent.spawn(f, 'https://www.python.org/'),
gevent.spawn(f, 'https://www.yahooar.com/'),
gevent.spawn(f, 'https://github.com/'),
])
print('time:',time.time() - start_time)
Synchronous & Asynchronous Execution 同步vs异步执行
并发的核心思想是一个更大的任务可以分解成多个子任务,其运行不依赖于其他任务的集合,因此可以异步运行 ,而不是一个在时间 同步。两个执行程序间的转换是一个关联转换。
在gevent中一个关联转换可以通过 yielding 来实现.在这个例子,两个程序的转换是通过调用 gevent.sleep(0).
import gevent
import random
def task(pid):
"""
Some non-deterministic task
"""
gevent.sleep(random.randint(0,2))
print('Task', pid, 'done')
def synchronous():
for i in range(1,10):
task(i)
def asynchronous():
coroutine_list = [gevent.spawn(task, i) for i in range(10)]
gevent.joinall(coroutine_list)
print('Synchronous:')
synchronous()
print('Asynchronous:')
asynchronous()