Python:通过select.epoll()检索多个URL

Python:通过select.epoll()检索多个URL,python,asynchronous,python-requests,urllib,epoll,Python,Asynchronous,Python Requests,Urllib,Epoll,我有一个面向事件的服务器,它已经使用了 现在应该解决一个新的要求:URL应该被获取(异步) 到目前为止,我一直使用请求库,而且我一直使用它是同步的,而不是异步的 如何将请求库(或不同的urllib)与linux e波尔结合使用 requests library文档对此有说明,但只提到异步框架(而不是select.epoll()): 我没有与select.epoll()结婚。到目前为止,一切都很顺利。如果可行,我可以使用不同的解决方案 背景:更大的问题是“我应该使用select.epoll()还是

我有一个面向事件的服务器,它已经使用了

现在应该解决一个新的要求:URL应该被获取(异步)

到目前为止,我一直使用请求库,而且我一直使用它是同步的,而不是异步的

如何将请求库(或不同的urllib)与linux e波尔结合使用

requests library文档对此有说明,但只提到异步框架(而不是select.epoll()):


我没有与select.epoll()结婚。到目前为止,一切都很顺利。如果可行,我可以使用不同的解决方案

背景:更大的问题是“我应该使用select.epoll()还是python拥有的众多异步框架之一?”。但StackOverflow的问题不能太广泛。这就是为什么这个问题集中在“通过select.epoll()检索多个URL”。如果你有更大问题的提示,请留下评论

如果您感到好奇,我在业余时间开发的一个小项目需要这个问题:(IPO是一个基于PostgreSQL的开源异步作业队列。)

如何将请求库(或不同的urllib)与linux-epoll结合使用? 不幸的是,除非在构建这样一个库时考虑到了这种集成,否则您不能这样做epoll,以及select/poll/kqueue,其他是I/O多路复用系统调用,需要围绕它构建整体程序体系结构

简单地说,一个典型的程序结构可以归结为以下几点

  • 你需要有一堆文件描述符(在你的例子中是非阻塞模式下的套接字)
  • 系统调用(man-epoll\u waitepoll的情况下)阻塞,直到一个或多个描述符上发生指定事件
  • 返回可用于I/O的描述符的信息
之后,外部代码的任务是处理这些描述符,即计算出有多少数据可用,调用一些回调等

如果库使用常规阻塞套接字,那么并行化它的唯一方法就是使用线程/进程 这是一个很好的主题,示例使用C,这很好,因为它更容易理解引擎盖下实际发生的事情

异步框架和请求库 让我们看看有什么建议

如果您担心阻塞IO的使用,那么有很多 将请求与Python的 异步性框架。下面是一些很好的例子 请求线程、grequests和请求期货)

请求线程-使用线程

grequests-与gevent集成(这是一个不同的故事,见下文)

请求未来——事实上也包括线程/进程

它们都与真正的异步性无关

我应该使用select.epoll()还是python提供的众多异步框架之一 请注意,epoll是特定于linux的beast,它不会工作,也就是说,在具有称为kqueue的不同机制的OS X上。因为您似乎正在编写一个通用作业队列,所以它似乎不是一个好的解决方案

现在回到python。您有以下选项:

线程/进程/concurrent.futures这不太可能是您的目标,因为您的应用程序是典型的服务器

epoll/kqueue-你必须自己做所有事情。在获取HTTP URL的情况下,您不仅需要处理HTTP/ssl,还需要处理异步DNS解析。还考虑使用[]提供一些基本的基础设施< /P> twisted/tornado-基于回调的框架已经为您完成了所有底层工作

gevent-如果要重用现有的阻塞库(urllib、请求等)并同时使用Python2.x和Python3.x,您可能会喜欢这一点。但这种解决方案是一种设计上的黑客行为。对于像你这样大小的应用程序来说,它可能还可以,但我不会用它来做任何更大的应用程序,这些应用程序应该是坚如磐石并在prod中运行的

asyncio

此模块提供了编写单线程的基础结构 使用协程的并发代码,通过套接字多路复用I/O访问 和其他资源,运行网络客户端和服务器,以及其他 相关原语

它有你可能需要的一切。 还有一些库使用流行的RDBMs和http

但它缺乏对Python2.x的支持。Python2.x有很多异步IO,但不确定它们有多稳定

最后 因此,如果我可以牺牲Python2.x,我个人会选择asyncio和相关的库


如果您真的需要python 2.x,请根据所需的稳定性和假设的峰值负载使用上述方法之一,在进行高性能开发时,我们总是根据自己的情况选择武器。因此,它仍然太宽泛,无法回答

但更大的问题更简单。只有IO绑定的程序适合异步

epoll和异步的目的是什么?避免CPU等待IO而什么也不做。CPU等待IO块,IO块是因为没有数据可读取或没有空间可写入

引入缓冲区是为了减少系统调用。当您在流上调用read时,实际上是从缓冲区读取。(概念,不是很准确)

Select或epoll是非阻塞繁忙轮询(epoll通过中断底层实现)

while true {
  for i in stream[]{
    if i has data
          read until unavailable
    }
}
这是愚蠢的,所以有选择和epoll。 每次从缓冲区读取数据时,都会有数据等待您,它是高速IO,然后epoll/select是您的最佳选择。当缓冲区总是空的时候,它是一个慢流,IO绑定,异步非常适合这种情况

我对异步不是很了解,对我来说是
from tornado import (httpserver, options,
                     ioloop, web, gen)
import time

import ujson as json
from collections import defaultdict

class Check(web.RequestHandler):

    @gen.coroutine
    def get(self):
        try:
            data = int(self.get_argument('data'))
        except ValueError:
            raise web.HTTPError(400, reason='Invalid value for data')

        delay = 100
        start = time.time()
        print('Processed: {!r}'.format(data))

       yield gen.Task(ioloop.IOLoop.instance().add_timeout, start + delay / 1000.)

        self.write('.')
        end = time.time()
        self.finish()


if __name__ == '__main__':
    port = 4545

    application = web.Application([
        (r'/get', Check)
        ])

    http_server = httpserver.HTTPServer(application)
    http_server.listen(port)
    print('Listening on port: {}'.format(port))
    ioloop.IOLoop.instance().start()
import grequests
from tornado.httpclient import HTTPClient
import time

def call_serial(num, httpclient):
    url = 'http://127.0.0.1:4545/get?data={}'.format(num)
    response = httpclient.fetch(url)
    print('Added: {!r}'.format(num))

def call_async(mapper):
    futures = (grequests.get(url) for url,_ in mapper)
    responses = grequests.map(futures)
    for response, (url,num) in zip(responses, mapper):
        print('Added: {!r}'.format(num))

def check(num):
    if num % 2 == 0:
        return False
    return True

def serial_calls(httpclient, up_to):
    for num in range(up_to):
        if check(num):
            call_serial(num, httpclient)

def async_calls(httpclient, up_to):
    mapper = []

    for num in range(up_to):
        if check(num):
            url = 'http://127.0.0.1:4545/get?data={}'.format(num)    
            mapper.append((url,num))

    call_async(mapper)


if __name__ == '__main__':

    httpclient = HTTPClient()

    print('SERIAL CALLS')
    serial_calls(httpclient, 100)

    print('ASYNC CALLS')
    async_calls(httpclient, 100)
    httpclient.close()