知乎自己在底层造了非常多的轮子,而且也在服务器部署方面和数据获取方面广泛使用 gevent 来提高并发获取数据的能力。现在开始我将结合实际使用与测试慢慢完善自己对 gevent 更全面的使用和扫盲。

在对 gevent loop 的使用上,gevent tutorial 介绍得非常敷衍,以至于完全不知道他的使用办法。这里我将结合 timeit 测试更详细的介绍一下 gevnet.loop 的使用。以及他的父类 Group 的使用。

其实在使用 gevent 上面我个人一直有一个误区,就是我使用并发的 gevent 一定比我平时线性的操作速度更快,其实不是这样。让我们来看一个例子:


import timeit
import gevent
def task1():
pass def task():
for i in range(50):
pass def async():
x = gevent.spawn(task)
x.join() def sync():
for i in range(50):
task1() print timeit.timeit(stmt=async, setup='''
from __main__ import task, async, sync
''', number=1000)
print '同步开始了'
print timeit.timeit(stmt=sync, setup='''
from __main__ import task, async, sync
''', number=1000) output:

0.0216090679169
同步开始了
0.00430107116699

可以看到,我们同样跑一样的函数调用,如果使用 gevent.spawn 一个调用,我们会话费更多的资源,这导致了我们甚至没有线性完成得快。你可能会说,这是当然了,因为这里只 spwan 了一个 gevent 的 greenlet 实例。如果我们调用多个呢?

import timeit
import gevent def async1():
p = []
for i in range(50):
p.append(gevent.spawn(task1))
gevent.joinall(p) def task1():
pass def sync():
for i in range(50):
task1() print timeit.timeit(stmt=async1, setup='''
from __main__ import task, async1, sync
''', number=1000)
print '同步开始了'
print timeit.timeit(stmt=sync, setup='''
from __main__ import task, async1, sync
''', number=1000) output:
1.21793103218
同步开始了
0.0048680305481

情况似乎变得更糟糕了。。。。我们同时 spawn 了 50个 greenlet 实例实图一次性搞定这个事情,但是速度甚至变得更慢了。由此我们可以得出一个结论,也许在并不是在网络请求或者需要等待切换的情况下,使用 gevent 也许不是一个很好的解决方案。

那到底种情况可以使我们的性能获得巨大的提升?来看这个例子:

import timeit
import gevent def async1():
p = []
for i in range(50):
p.append(gevent.spawn(task1))
gevent.joinall(p) def task1():
gevent.sleep(0.001) def sync():
for i in range(50):
task1() print timeit.timeit(stmt=async1, setup='''
from __main__ import task1, async1, sync
''', number=100)
print '同步开始了'
print timeit.timeit(stmt=sync, setup='''
from __main__ import task1, async1, sync
''', number=100) output:
0.25629901886
同步开始了
6.91364789009

可以看出来,这次我 spawn 50个一起跑,就远远快于线性了。因为在线性的情况下,我们每次都会在 task1 任务运行的时候阻塞 0.001s, 但是 gevent 使得 async 函数几乎不受等待影响。非常快速的解决了这个问题。其实这个环境在我们进行网络 io 的时候非常常见。比如我们向某个地址下载图片,如果我们线性下载图片,我们需要等待第一张图片下载完成之后才能进行第二张图片的下载,但是我们使用 gevent 并发下载图片,我们可以先开始下载图片,然后在等待的时候切换到别的任务继续进行下载。当下载完毕之后我们会切换回来完成下载。不用等待任何一个任务下载完成,大大的提高了效率。

gevent 的 pool 函数可以控制并发的时候最多使用 greenlet 的数量。 这里我循环了50次,但是当我们在进行 io 的时候,我们设置了 1w 次,那么也会起 10000 个协程来运行这个程序,对于性能我们是不知道的。有可能会直接堵死服务器端,所以我们需要对此进行控制,我们限制最多同时使用 20 个 greenlet 实例进行处理,当有任务完成之后我们再开始别的任务,更好的控制我们的请求以及维护相当的效率让我们来看几个数据:

开 10个 greenlet 的情况

import timeit
import gevent
from gevent.pool import Pool x = Pool(40) def async1():
for i in range(50):
x.spawn(task1)
x.join() def task1():
gevent.sleep(0.001) def sync():
for i in range(50):
task1() print timeit.timeit(stmt=async1, setup='''
from __main__ import task1, async1, sync
''', number=100)
print '同步开始了'
print timeit.timeit(stmt=sync, setup='''
from __main__ import task1, async1, sync
''', number=100) output:

0.813331842422
同步开始了
6.89506411552

 

开 40 个实例的情况:

0.366757154465
同步开始了
6.78097295761

开80 个实例的情况:

0.222685098648
同步开始了
6.77246403694

开10000个的情况:

0.227874994278
同步开始了
6.81039714813

可以看到当我们超过阀值之后,开更多的实例已经没有任何意义了。而且有可能还造成一些性能上的浪费,所以选择一个合适的实例数量即可。

另外还有一个速度更快的函数可以提供使用:

def async1():
for i in range(50):
x.imap(task1)

官方文档上还有一句话,就是如果对出的结果并不要求顺序的话可以使用imap_unordered,速度更快:

def async1():
for i in range(50):
x.imap_unordered(task1)

pool饱和的情况下 上面的例子差不多只要 0.8s 就能处理完,imap 需要1s。使用join需要 0.22s。

Reference:

http://hhkbp2.github.io/gevent-tutorial/#_8  gevent-tutorial

Gevent 性能和 gevent.loop 的运用和带来的思考的更多相关文章

  1. python3下multiprocessing、threading和gevent性能对比----暨进程池、线程池和协程池性能对比

    python3下multiprocessing.threading和gevent性能对比----暨进程池.线程池和协程池性能对比   标签: python3 / 线程池 / multiprocessi ...

  2. {python之协程}一 引子 二 协程介绍 三 Greenlet 四 Gevent介绍 五 Gevent之同步与异步 六 Gevent之应用举例一 七 Gevent之应用举例二

    python之协程 阅读目录 一 引子 二 协程介绍 三 Greenlet 四 Gevent介绍 五 Gevent之同步与异步 六 Gevent之应用举例一 七 Gevent之应用举例二 一 引子 本 ...

  3. 并发编程 - 协程 - 1.协程概念/2.greenlet模块/3.gevent模块/4.gevent实现并发的套接字通信

    1.协程并发:切+保存状态单线程下实现并发:协程 切+ 保存状态 yield 遇到io切,提高效率 遇到计算切,并没有提高效率 检测单线程下 IO行为 io阻塞 切 相当于骗操作系统 一直处于计算协程 ...

  4. python全栈开发,Day43(引子,协程介绍,Greenlet模块,Gevent模块,Gevent之同步与异步)

    昨日内容回顾 I/O模型,面试会问道 I/O操作,不占用CPU,它内部有一个专门的处理I/O模块 print和写log属于I/O操作,它不占用CPU 线程 GIL保证一个进程中的多个线程在同一时刻只有 ...

  5. gevent调度流程解析

    gevent是目前应用非常广泛的网络库,高效的轮询IO库libev加上协程(coroutine),使得gevent的性能非常出色,尤其是在web应用中.本文介绍gevent的调度流程,主要包括geve ...

  6. Python之路(第四十七篇) 协程:greenlet模块\gevent模块\asyncio模块

    一.协程介绍 协程:是单线程下的并发,又称微线程,纤程.英文名Coroutine.一句话说明什么是线程:协程是一种用户态的轻量级线程,即协程是由用户程序自己控制调度的. 协程相比于线程,最大的区别在于 ...

  7. 协程--gevent模块(单线程高并发)

    先恶补一下知识点,上节回顾 上下文切换:当CPU从执行一个线程切换到执行另外一个线程的时候,它需要先存储当前线程的本地的数据,程序指针等,然后载入另一个线程的本地数据,程序指针等,最后才开始执行.这种 ...

  8. python gevent 协程

    简介 没有切换开销.因为子程序切换不是线程切换,而是由程序自身控制,没有线程切换的开销,因此执行效率高, 不需要锁机制.因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断 ...

  9. Python自动化之select、greenlet和gevent和事件驱动模型初探

    进程.线程和协程的区别 进程拥有自己独立的堆和栈,既不共享堆,亦不共享栈,进程由操作系统调度. 线程拥有自己独立的栈和共享的堆,共享堆,不共享栈,线程亦由操作系统调度(标准线程是的). 协程和线程一样 ...

随机推荐

  1. SpringMVC Controller 返回值几种类型

    SpringMVC Controller 返回值几种类型 2016年06月21日 19:31:14 为who而生 阅读数:4189 标签: Controller 返回值类型spring mvc 更多 ...

  2. 【转】用ffmpeg转多音轨的mkv文件

    命令: ffmpeg -i AmericanCaptain.mkv -map 0:v -vcodec copy -map 0:a:1 -acodec copyAmericanCaptain.mp4 - ...

  3. python中numpy.pad简单填充0用法

    # -*- coding: utf-8 -*-"""Created on Sun Apr 28 22:07:02 2019 @author: jiangshan" ...

  4. keepalived+lvs子网掩码造成VIP切换故障 + vrrp_script+track_script

    keepalived+lvs子网掩码造成VIP切换故障 架构:keepalived+lvs ,前端调度器是双主模型 现象:keepalived手动停掉一台,但是虚拟IP不会切换 整体网络是24位 VI ...

  5. PHP基础介绍

    php之基本操作 1.常用数据类型: 字符串.整形.浮点数.逻辑.数组.对象.NULL. 字符串: $x = "hello"; 整形:$x = 123; 浮点数:$x =1.123 ...

  6. ASP.NET Core如何使用WSFederation身份认证集成ADFS

    如果要在ASP.NET Core项目中使用WSFederation身份认证,首先需要在项目中引入NuGet包: Microsoft.AspNetCore.Authentication.WsFedera ...

  7. 内联函数:static inline 和 extern inline 的含义

    引入内联函数的目的是为了解决程序中函数调用的效率问题. 函数是一种更高级的抽象.它的引入使得编程者只关心函数的功能和使用方法,而不必关心函数功能的具体实现:函数的引入可以减少程序的目标代码,实现程序代 ...

  8. Django之连接多个数据库的相关配置

    01-修改django默认的数据库 # settings.py DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NA ...

  9. 无线网络中信噪比(SNR)计算

    信噪比(S/N)=log[信号功率密度/噪声功率密度] a =log[信号功率密度]-log[噪声功率密度] 例如,接收端的信号功率密度为-63dBm,噪声的信号功率密度为-95dBm,则: 信噪比( ...

  10. 建立一个单链表,并删除链表中值为W的元素

    #include<iostream> #include<algorithm> #include<string.h> #include<stdio.h> ...