9

我最近尝试使用多处理模块(它是工人池)。我在这里阅读了几个关于多线程(与标准的非线程版本相比减慢了整个过程)和多处理的讨论,但我找不到一个(可能非常简单)问题的答案:

您可以通过多处理加速 url 调用还是不是像网络适配器这样的瓶颈?我没有看到例如 urllib2-open-method 的哪一部分可以并行化,以及它应该如何工作......

编辑:这是我想加快的请求和当前的多处理设置:

 urls=["www.foo.bar", "www.bar.foo",...]
 tw_url='http://urls.api.twitter.com/1/urls/count.json?url=%s'

 def getTweets(self,urls):
    for i in urls:
        try:
            self.tw_que=urllib2.urlopen(tw_url %(i))
            self.jsons=json.loads(self.tw_que.read())
            self.tweets.append({'url':i,'date':today,'tweets':self.jsons['count']})
        except ValueError:
            print ....
            continue
    return self.tweets 

 if __name__ == '__main__':
    pool = multiprocessing.Pool(processes=4)            
    result = [pool.apply_async(getTweets(i,)) for i in urls]
    [i.get() for i in result]
4

5 回答 5

7

啊,又是关于 GIL 的讨论。事情就是这样。使用 urllib2 获取内容将主要受 IO 限制。当任务受 IO 限制时,本机线程和多处理将具有相同的性能(线程仅在受 CPU 限制时才会成为问题)。是的,您可以加快速度,我自己使用 python 线程和 10 个下载器线程完成了它。

基本上,您使用生产者-消费者模型,其中一个线程(或进程)生成要下载的 url,而N个线程(或进程)从该队列消费并向服务器发出请求。

这是一些伪代码:

# Make sure that the queue is thread-safe!!

def producer(self):
    # Only need one producer, although you could have multiple
    with fh = open('urllist.txt', 'r'):
        for line in fh:
            self.queue.enqueue(line.strip())

def consumer(self):
    # Fire up N of these babies for some speed
    while True:
        url = self.queue.dequeue()
        dh = urllib2.urlopen(url)
        with fh = open('/dev/null', 'w'): # gotta put it somewhere
            fh.write(dh.read())

现在,如果您正在下载非常的数据块(数百 MB)并且单个请求完全使带宽饱和,那么运行多个下载是没有意义的。您运行多个下载(通常)的原因是因为请求很小并且具有相对较高的延迟/开销。

于 2011-08-01T23:52:13.770 回答
6

看看gevent,特别是这个例子:concurrent_download.py。它比多处理和多线程要快得多 + 它可以轻松处理数千个连接。

于 2011-08-02T18:19:55.960 回答
3

这取决于!您是在联系不同的服务器,传输的文件是小还是大,您是否浪费了很多时间等待服务器回复或传输数据,...

通常,多处理涉及一些开销,因此您希望确保通过并行工作获得的加速比开销本身更大。

另一点:网络和因此 I/O 绑定的应用程序使用异步 I/O 和事件驱动架构而不是线程或多处理可以更好地工作和扩展,因为在此类应用程序中,大部分时间都花在等待 I/O 而不是做任何计算。

对于您的具体问题,我会尝试使用TwistedgeventTornado或任何其他不使用线程来并行化连接的网络框架来实现解决方案。

于 2011-08-01T23:52:47.870 回答
1

当您将 Web 请求拆分到多个进程时,您所做的是并行化网络延迟(即等待响应)。所以你通常应该得到一个很好的加速,因为大多数进程应该大部分时间都在休眠,等待一个事件。

或者使用 Twisted。;)

于 2011-08-01T23:52:56.720 回答
0

如果您的代码被破坏,则没有任何用处:(f()带括号)立即调用 Python 中的函数,您应该只传递f(不带括号)以在池中执行。您来自问题的代码:

#XXX BROKEN, DO NOT USE
result = [pool.apply_async(getTweets(i,)) for i in urls]
[i.get() for i in result]

注意后面的括号getTweets表示所有代码都在主线程中串行执行。

将调用委托给池:

all_tweets = pool.map(getTweets, urls)

此外,您不需要单独的进程,除非json.loads()您的情况很昂贵(CPU 方面)。您可以使用线程:替换multiprocessing.Poolmultiprocessing.pool.ThreadPool-- 其余部分相同。GIL 在 CPython 的 IO 期间释放,因此如果大部分时间都花在urlopen().read().

这是一个完整的代码示例

于 2015-12-11T18:53:55.830 回答