python之yield(二)

前言

建立前文提到的yield基本应用,协程概念之后,在续一些协程衍生出的异步应用。经常会听到并发和并行,同步和异步那么就简单梳理下这几个到底应该怎样理解。

并发和并行

并发:是程序结构上的概念,指你实现的程序是否支持多个动作。所以并发是指:你实现的程序是并发的,或者说你实现的系统是并发的,实质就是你实现的系统支持多个动作。例如你这个人既支持画图,也支持写文章这个两个动作,说明你“人”这个系统是并发的。
并行:指在同一时刻,同时执行多个操作。这个从直观感觉上说,就是并行指程序运行过程中,否同时执行多个事件。直观感受,单核操作系统是不能达到并行目的,因为一个cpu一个时间只能执行一个东西,多核才是执行并行的基础。既有两个人,同时执行画图,写文章这两件事。同一时间点,这两件事都在执行。

同步和异步

同步:发起一个调用,没得到调用结果,就不返回。
异步:发起一个调用结果,没等调用结束,就已经返回。

阻塞和非阻塞

阻塞:线程和进程在访问数据时遇到需要等待返回时执行等待。
非阻塞:线程和进程访问数据时遇到等待返回,挂起当前线程或进程,转而执行其他线程或进程。

一个很好的示例说明同步阻塞,同步非阻塞,异步阻塞,异步费阻塞的示例:
同步阻塞:小明一直盯着下载进度条,到 100% 的时候就完成。
同步非阻塞:小明提交下载任务后就去干别的,每过一段时间就去瞄一眼进度条,看到 100% 就完成。
异步阻塞:小明换了个有下载完成通知功能的软件,下载完成就“叮”一声。不过小明仍然一直等待“叮”的声音(看起来很傻,不是吗)
异步非阻塞:仍然是那个会“叮”一声的下载软件,小明提交下载任务后就去干别的,听到“叮”的一声就知道完成了。

实际使用

结合我们之前描述的协程概念可以知道,我们使用协程实现的程序就是异步非阻塞。这一套实用性,可以说非常广泛。举个例子:
用过mongodb都知道,如果要是在海量数据中match一些复合条件的数据出来,一定会有一个等待的时间,且假定你的程序是单进程,单线程的。那么现在要求运行5个match数据的操作,每个操作都是一秒。通常阻塞的就会按部就班一个一个运行,最终至少5秒得到结果。可是如果你运用异步机制,每个match操作在读取停顿,立马返回主调函数,继续执行下一个match,一次类推,这样相当于5个match在同时等待,最终耗时1秒多。
如果换多线程使用:看似并行,别忘了python中GIL限制,实际上也是单线程执行的,只是系统会自动切换。那么增加了操作系统线程间切换的开销,一定不会快于上面的协程实现。因为这个程序主要耗时是等待从mongodb中读取数据。
如果换做多进程:这个是真实并行,但是效率同协程比起来,基本相同,可是这个会增加系统额外的内存开销,因为每个进程都要占据自己的独立运行空间。
所以对于IO(数据库读写,网络访问,文件读写等)密集型的,协程能够极大提高效率。因为这是我们人为知道在等待耗时的地方调出继续执行。

Gevent

概述

Gevent是一个python第三方库来支持协程,并不是简单的让编者自己用yield实现。其以greenlet为核心,pip在安装gevent时会自动安装greenlet。Gevent框架实现异,也是利用了linux epoll事件监听机制。

用法

具体用法我们通过一个示例了解:

import gevent

def foo():
    print('Running in foo')
    gevent.sleep(0) #不同于time.sleep(n)
    print('Explicit context switch to foo again')

def bar():
    print('Explicit context to bar')
    gevent.sleep(0)
    print('Implicit context switch back to bar')

gevent.joinall([
    gevent.Greenlet(foo).start()
    gevent.spawn(bar),
])

执行结果:
这里写图片描述

这里的gevent.sleep(n)和我们通常理解的time.sleep(n)不同源码注释中解释说:让当前greenlet休眠至少n秒,但是当n<=0时,立马将执行权交给其他的能运行的greenlet。说白了n置0,立马调出当前函数,下次轮到他执行的时候继续从gevent.sleep(n)后执行。可以去看一下gevent.sleep(seconds=0, ref=True)源码,很简单。

怎么样,foo和bar两个函数并没有yield,但是实现了协程。这个有点僵硬,通常写东西经常要访问别人写好的一些基础服务的API,示例如下:

import gevent
import time
import requests

tic = lambda start : 'at %1.1f seconds' % (time.time() - start)

urls = [
    ('google', 'https://www.google.com.hk/'),
    ('github', 'https://github.com/'),
    ('baidu', 'https://www.baidu.com/'),
]

def fetch(key, url):
    start = time.time()
    response = requests.get(url)
    print('Process {0}: {1} -- consuming: {2}'.format(
        key, response.status_code, tic(start)))

def synchronous():
    start = time.time()
    for obj in urls:
        fetch(obj[0], obj[1])
    print('End synchronous: consuming: {}'.format(tic(start)))

def asynchronous():
    start = time.time()
    coroutine = []
    for obj in urls:
        coroutine.append(gevent.spawn(fetch, obj[0], obj[1]))
    gevent.joinall(coroutine)
    print('End synchronous: consuming: {}'.format(tic(start)))

print('Synchronous:')
synchronous()

print('Asynchronous:')
asynchronous()

该段程序运行结果:
这里写图片描述
仔细观察上图运行结果,思考几个问题:
1, 重复运行,synchronous返回结果的顺序是不是一定是google, github, baidu? 相对应Asynchronous呢?
2,synchronous总用时?Asynchronous总共用时?
不妨自己尝试运行一下。

在学习Gevent过程中,http://xlambda.com/gevent-tutorial/#_4
让我收获颇丰,其中示例第一个源于此链接。

除此之外,异步在python中真正一个大的框架是Tornado,是在牛的一个框架,很轻,但是却能够解决c10k,后续有机会再行分享。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值