使用 Gevent 网络框架

发布于 2021-04-09 12:05:30 字数 5332 浏览 1208 评论 0

官方网站是这么介绍 Gevent:

gevent is a coroutine-based Python networking library that uses greenlet to provide a high-level synchronous API on top of the libevent event loop.

简单翻译过来就是:gevent 一个基于协程的Python网络库,依赖于 libevent 的 event loop 使用 greenlet 提供高级同步API。

这段话简单描述了 gevent 的架构实现和技术,不过初学者看了还是一脸茫然。我能想到的能最快让人理解的定义是:

gevent 给了你线程,但是没有使用线程

为什么不使用线程

为什么不使用线程呢?线程最大的缺点对我来说就是相比较 greenlets(使用在gevent中的类线程的抽象概念)来说它会占用大量资源。 例如:这个模拟 helloworld webserver 的小程序,下面是没有使用任何并发的代码:

import sys
import socket
import time

def sequential(port):
    s = socket.socket()
    s.bind(('0.0.0.0', port))
    s.listen(500)

    while True:
        cli, addr = s.accept()
        handle_request(cli, time.sleep)

def handle_request(s, sleep):
    try:
        s.recv(1024)
        sleep(0.1)
        s.send('''HTTP/1.0 200 Ok 

HelloWorld''')
        s.shutdown(socket.SHUT_WR)
        print '.',
    except Exception, ex:
        print 'e', ex,
    finally:
        sys.stdout.flush()
        s.close()

if __name__ == '__main__':
    sequential(int(sys.argv[1]))

这段代码使用 sleep,目的是是减慢 handle_request 方法使它更真实。使用 Apache 的性能测试工具做大并发测试,然而我们得到很糟糕的结果。运行: ab -r -n 200 -c 200 http://lcoalhost:1111/

结果:

Benchmarking localhost (be patient)
Completed 100 requests
apr_pollset_poll: The timeout specified has expired (70007)
Total of 196 requests Completed

到最后超时了。

也许用线程会更好,那么用 threads 函数替换 sequential 函数:

import threading

def thread(port):
    s = socket.socket()
    s.bind(('0.0.0.0', port))
    s.listen(500)
    while True:
        cli, addr = s.accept()
        t = threading.Thread(target=handle_request, args=(cli, time.sleep))
        t.daemon = True
        t.start()

结果:

Benchmarking localhost (be patient)
Completed 100 requests
Completed 200 requests
Finished 200 requests


Server Software:        
Server Hostname:        localhost
Server Port:            1115

Document Path:          /
Document Length:        10 bytes

Concurrency Level:      200
Time taken for tests:   0.229 seconds
Complete requests:      200
Failed requests:        0
Write errors:           0
Total transferred:      5600 bytes
HTML transferred:       2000 bytes
Requests per second:    874.02 [#/sec] (mean)
Time per request:       228.827 [ms] (mean)
Time per request:       1.144 [ms] (mean, across all concurrent requests)
Transfer rate:          23.90 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    5   2.9      5      11
Processing:   101  107   3.8    107     116
Waiting:      101  107   3.9    106     115
Total:        105  112   1.5    112     116

Percentage of the requests served within a certain time (ms)
  50%    112
  66%    113
  75%    113
  80%    114
  90%    114
  95%    115
  98%    115
  99%    116
 100%    116 (longest request)

运行 ab -r -n 200 -c 200,总共花时是 0.229 秒,现在我们用 gevent 做类线程的模拟操作:

import gevent
def greenlet(port):
    from gevent import socket
    s = socket.socket()
    s.bind(('0.0.0.0', port))
    s.listen(500)
    while True:
        cli, addr = s.accept()
        gevent.spawn(handle_request, cli, gevent.sleep)    

结果:

Benchmarking localhost (be patient)
Completed 100 requests
Completed 200 requests
Finished 200 requests


Server Software:        
Server Hostname:        localhost
Server Port:            1115

Document Path:          /
Document Length:        0 bytes

Concurrency Level:      200
Time taken for tests:   0.012 seconds
Complete requests:      200
Failed requests:        597
   (Connect: 0, Receive: 398, Length: 0, Exceptions: 199)
Write errors:           0
Total transferred:      0 bytes
HTML transferred:       0 bytes
Requests per second:    16837.85 [#/sec] (mean)
Time per request:       11.878 [ms] (mean)
Time per request:       0.059 [ms] (mean, across all concurrent requests)
Transfer rate:          0.00 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.0      0       0
Processing:     0    2   2.2      4       5
Waiting:        0    0   0.0      0       0
Total:          0    2   2.2      4       5

Percentage of the requests served within a certain time (ms)
  50%      4
  66%      4
  75%      4
  80%      4
  90%      5
  95%      5
  98%      5
  99%      5
 100%      5 (longest request)

我们看到总共花时不到 0.012 秒。

为什么不要一直使用 gevent/greenlets 呢?

为什么不要一直在 gevent 中 greenlet?主要 Greenlets 使用协助式多任务,而线程使用抢占式多任务,意味着一个 greenlet 永远不会停止执行来让给另外的 greenlet 执行,除非它使用确切的 yielding 函数,例如 gevent.socket.socket.recv 或 gevent.sleep,而线程完全是基于操作系统决定线程之间的切换的。

如果你使用 python 一段时间了,你应该听说过关于全局解释锁 GIL,它只允许在同一时刻单个线程执行 python 字节码。所以尽管在 python 中有线程和并发,但是线程所提供的好处不及 C 或者 Java。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

JSmiles

生命进入颠沛而奔忙的本质状态,并将以不断告别和相遇的陈旧方式继续下去。

0 文章
0 评论
84961 人气
更多

推荐作者

daid

文章 0 评论 0

我心依旧

文章 0 评论 0

晒暮凉

文章 0 评论 0

微信用户

文章 0 评论 0

DS

文章 0 评论 0

〆凄凉。

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文