天天看点

python协程

基础

http://yeqianfeng.me/python-yield-expression/

国外写的通俗易懂的

http://www.dabeaz.com/coroutines/index.html

协程的好处,处理io有优势。

gevent实现协程案例

多进程+协程下,避开了CPU切换的开销,又能把多个CPU充分利用起来,这种方式对于数据量较大的爬虫还有文件读写之类的效率提升是巨大的。

多进程+协程的问题,是怎么控制进程池的问题,可以看看gevent.pool这个协程池

当我们面对如下的环境时,事件驱动模型通常是一个好的选择:

程序中有许多任务,而且…

任务之间高度独立(因此它们不需要互相通信,或者等待彼此)而且…

在等待事件到来时,某些任务会阻塞。

当应用程序需要在任务间共享可变的数据时,这也是一个不错的选择,因为这里不需要采用同步处理。

网络应用程序通常都有上述这些特点,这使得它们能够很好的契合事件驱动编程模型。

协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。因此:

协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,换种说法:进入上一次离开时所处逻辑流的位置。

协程,使用它之前我们先讲讲what/why/how(它是什么/为什么用它/怎么使用它)

基本认识

代码

总结

计算机分为IO bound 和CPU bound两种类型的task

参考 http://blog.csdn.net/u014745194/article/details/71657575

Coroutine

协程其实可以认为是比线程更小的执行单元。 为啥说他是一个执行单元,因为他自带CPU上下文

我们可以把一个协程 切换到另一个协程。 只要这个过程中保存或恢复 CPU上下文那么程序还是可以运行的

协程和线程的差异

线程切换从系统层面远不止保存和恢复 CPU上下文这么简单

操作系统为了程序运行的高效性每个线程都有自己缓存Cache等等数据,操作系统还会帮你做这些数据的恢复操作。 所以线程的切换非常耗性能。但是协程的切换只是单纯的操作CPU的上下文,所以一秒钟切换个上百万次系统都抗的住

协程带来的问题

目前的协程框架一般都是设计成 1:N 模式。所谓 1:N 就是一个线程作为一个容器里面放置多个协程。 那么谁来适时的切换这些协程?答案是有协程自己主动让出CPU,也就是每个协程池里面有一个调度器, 这个调度器是被动调度的。意思就是他不会主动调度。而且当一个协程发现自己执行不下去了(比如异步等待网络的数据回来,但是当前还没有数据到), 这个时候就可以由这个协程通知调度器,这个时候执行到调度器的代码,调度器根据事先设计好的调度算法找到当前最需要CPU的协程。 切换这个协程的CPU上下文把CPU的运行权交个这个协程,直到这个协程出现执行不下去需要等等的情况,或者它调用主动让出CPU的API之类,触发下一次调度。

调度器<----协程,调度器---->协程

协程的好处

在高IO密集型的程序下很好。但是高CPU密集型的程序下没啥好处

greenlet版本实现协程案例,看起来好容易

import threading

import asyncio

@asyncio.coroutine

def hello():

    print('Hello world! (%s)' % threading.currentThread())

    yield from asyncio.sleep(1)

    print('Hello again! (%s)' % threading.currentThread())

loop = asyncio.get_event_loop()

tasks = [hello(), hello()]

#loop.run_until_complete(asyncio.wait(tasks))

for i in tasks:

    loop.run_until_complete(i)

loop.close()

从注释这个,可以看出协程的作用了

asyncio.wait,通过它可以获取一个协同程序的列表,同时返回一个将它们全包括在内的单独的协同程序

当两个hello方法,放入一个wait中,会同时执行,意味着没有阻塞的部分,会同时执行,然后必须等到阻塞完成后,同时返回,并继续循环。

def wget(host):

    print('wget %s...' % host)

    connect = asyncio.open_connection(host, 80)

    reader, writer = yield from connect

    header = 'GET / HTTP/1.0\r\nHost: %s\r\n\r\n' % host

    writer.write(header.encode('utf-8'))

    yield from writer.drain()

    #不甚了解asyncio模块的writer函数, 但是从代码来看, 它是直接写出到TCP连接的, 也就是http的sever端

    while True:

        line = yield from reader.readline()

        yield from asyncio.sleep(1)

        if line == b'\r\n':

        #readline函数如果返回了\r\n就说明这一行是空行;

            break

        print('%s header > %s' % (host, line.decode('utf-8').rstrip()))

    # Ignore the body, close the socket

    writer.close()

tasks = [wget(host) for host in ['www.sina.com.cn', 'www.sohu.com', 'www.163.com']]

loop.run_until_complete(asyncio.wait(tasks))

await是异步等待, 既然你已经知道了控制权会被交出去, 那么就说明他自己是不可能自己恢复的, 必须等待外部程序(python解释器)利用send重新启动切换时的执行现场.

协程用自己的话,来说,就是并行执行多个任务,自己定义切换任务的顺序,对于io等待类应用有用。

反正碰到yield from 理解为阻塞就行了。

本文转自 liqius 51CTO博客,原文链接:http://blog.51cto.com/szgb17/1941003,如需转载请自行联系原作者