Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/three.js/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Python 一个非常简单的多线程并行URL获取(无队列)_Python_Multithreading_Callback_Python Multithreading_Urlfetch - Fatal编程技术网

Python 一个非常简单的多线程并行URL获取(无队列)

Python 一个非常简单的多线程并行URL获取(无队列),python,multithreading,callback,python-multithreading,urlfetch,Python,Multithreading,Callback,Python Multithreading,Urlfetch,我花了一整天的时间在Python中寻找最简单的多线程URL获取程序,但我发现的大多数脚本都使用队列、多处理或复杂的库 最后,我自己写了一篇文章,作为一个回答。请随时提出改进建议 我猜其他人可能也在寻找类似的内容。此脚本从数组中定义的一组URL获取内容。它为每个要获取的URL生成一个线程,因此它用于有限的一组URL 每个线程不使用队列对象,而是通过对全局函数的回调来通知其结束,该回调保持运行线程数的计数 import threading import urllib2 import time st

我花了一整天的时间在Python中寻找最简单的多线程URL获取程序,但我发现的大多数脚本都使用队列、多处理或复杂的库

最后,我自己写了一篇文章,作为一个回答。请随时提出改进建议


我猜其他人可能也在寻找类似的内容。

此脚本从数组中定义的一组URL获取内容。它为每个要获取的URL生成一个线程,因此它用于有限的一组URL

每个线程不使用队列对象,而是通过对全局函数的回调来通知其结束,该回调保持运行线程数的计数

import threading
import urllib2
import time

start = time.time()
urls = ["http://www.google.com", "http://www.apple.com", "http://www.microsoft.com", "http://www.amazon.com", "http://www.facebook.com"]
left_to_fetch = len(urls)

class FetchUrl(threading.Thread):
    def __init__(self, url):
        threading.Thread.__init__(self)
        self.setDaemon = True
        self.url = url

    def run(self):
        urlHandler = urllib2.urlopen(self.url)
        html = urlHandler.read()
        finished_fetch_url(self.url)


def finished_fetch_url(url):
    "callback function called when a FetchUrl thread ends"
    print "\"%s\" fetched in %ss" % (url,(time.time() - start))
    global left_to_fetch
    left_to_fetch-=1
    if left_to_fetch==0:
        "all urls have been fetched"
        print "Elapsed Time: %ss" % (time.time() - start)


for url in urls:
    "spawning a FetchUrl thread for each url to fetch"
    FetchUrl(url).start()
中的主要示例可以做您想要的一切,简单得多。另外,它可以通过一次只处理5个URL来处理大量URL,并且可以更好地处理错误

当然,这个模块只内置于Python3.2或更高版本中……但是如果您使用的是2.5-3.1,您可以只安装后端口,脱离PyPI。对于示例代码,您只需将
concurrent.futures
搜索并替换为
futures
,对于2.x,将
urllib.request
替换为
urllib2

以下是后端口到2.x的示例,已修改为使用URL列表并添加时间:

import concurrent.futures
import urllib2
import time

start = time.time()
urls = ["http://www.google.com", "http://www.apple.com", "http://www.microsoft.com", "http://www.amazon.com", "http://www.facebook.com"]

# Retrieve a single page and report the url and contents
def load_url(url, timeout):
    conn = urllib2.urlopen(url, timeout=timeout)
    return conn.readall()

# We can use a with statement to ensure threads are cleaned up promptly
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
    # Start the load operations and mark each future with its URL
    future_to_url = {executor.submit(load_url, url, 60): url for url in urls}
    for future in concurrent.futures.as_completed(future_to_url):
        url = future_to_url[future]
        try:
            data = future.result()
        except Exception as exc:
            print '%r generated an exception: %s' % (url, exc)
        else:
            print '"%s" fetched in %ss' % (url,(time.time() - start))
print "Elapsed Time: %ss" % (time.time() - start)

但你可以让这更简单。实际上,您所需要的只是:

def load_url(url):
    conn = urllib2.urlopen(url, timeout)
    data = conn.readall()
    print '"%s" fetched in %ss' % (url,(time.time() - start))
    return data
    
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
    pages = executor.map(load_url, urls)

print "Elapsed Time: %ss" % (time.time() - start)

我现在发布了一个不同的解决方案,让工作线程不是deamon,并将它们连接到主线程(这意味着阻止主线程,直到所有工作线程都完成),而不是通过回调全局函数通知每个工作线程的执行结束(正如我在前面的回答中所做的那样),正如在一些评论中指出的那样,这种方式不是线程安全的

import threading
import urllib2
import time

start = time.time()
urls = ["http://www.google.com", "http://www.apple.com", "http://www.microsoft.com", "http://www.amazon.com", "http://www.facebook.com"]

class FetchUrl(threading.Thread):
    def __init__(self, url):
        threading.Thread.__init__(self)
        self.url = url

    def run(self):
        urlHandler = urllib2.urlopen(self.url)
        html = urlHandler.read()
        print "'%s\' fetched in %ss" % (self.url,(time.time() - start))

for url in urls:
    FetchUrl(url).start()

#Join all existing threads to main thread.
for thread in threading.enumerate():
    if thread is not threading.currentThread():
        thread.join()

print "Elapsed Time: %s" % (time.time() - start)

尽可能简化原始版本:

import threading
import urllib2
import time

start = time.time()
urls = ["http://www.google.com", "http://www.apple.com", "http://www.microsoft.com", "http://www.amazon.com", "http://www.facebook.com"]

def fetch_url(url):
    urlHandler = urllib2.urlopen(url)
    html = urlHandler.read()
    print "'%s\' fetched in %ss" % (url, (time.time() - start))

threads = [threading.Thread(target=fetch_url, args=(url,)) for url in urls]
for thread in threads:
    thread.start()
for thread in threads:
    thread.join()

print "Elapsed Time: %s" % (time.time() - start)
这里唯一的新技巧是:

  • 跟踪您创建的线程
  • 如果你只是想知道线程的计数器什么时候都完成了,那么就不用麻烦了
    join
    已经告诉你了
  • 如果不需要任何状态或外部API,则不需要
    线程
    子类,只需要
    目标
    函数

多处理
有一个不会启动其他进程的线程池:

#!/usr/bin/env python
from multiprocessing.pool import ThreadPool
from time import time as timer
from urllib2 import urlopen

urls = ["http://www.google.com", "http://www.apple.com", "http://www.microsoft.com", "http://www.amazon.com", "http://www.facebook.com"]

def fetch_url(url):
    try:
        response = urlopen(url)
        return url, response.read(), None
    except Exception as e:
        return url, None, e

start = timer()
results = ThreadPool(20).imap_unordered(fetch_url, urls)
for url, html, error in results:
    if error is None:
        print("%r fetched in %ss" % (url, timer() - start))
    else:
        print("error fetching %r: %s" % (url, error))
print("Elapsed Time: %s" % (timer() - start,))
与基于
线程的解决方案相比,该解决方案具有以下优势:

  • ThreadPool
    允许限制最大并发连接数(
    20
    在代码示例中)
  • 由于所有输出都在主线程中,因此输出没有被篡改
  • 将记录错误
  • 代码在Python2和Python3上都可以工作,没有任何更改(假设Python3上的
    来自urllib.request import urlopen

我认为这非常有用!谢谢:)在没有锁的情况下修改共享全局文件是不安全的。而且做
urlsToFetch-=1
这样的事情尤其危险。在解释器内部,它编译为三个单独的步骤,以加载
urlsToFetch
,减去一个,并存储
urlsToFetch
。如果解释器在加载和存储之间切换线程,那么最终线程1将加载2,然后线程2将加载相同的2,然后线程2将存储1,然后线程1将存储1.hi abarnert,谢谢您的回答。您能建议一个线程安全的解决方案吗?非常感谢您可以在对变量的每次访问周围放置一个
线程。锁定
,或许多其他可能性(使用计数信号量而不是普通整数,或使用屏障而不是显式计数,…),但您实际上根本不需要这个全局变量。只要
加入所有线程,而不是对它们进行后台监控,这在你加入所有线程后就完成了。事实上,像这样对线程进行后台监控,然后不等待任何操作,这意味着你的程序会退出,终止所有的工作线程,而大多数线程都无法完成。在一台网络连接缓慢的MacBook Pro上,我经常在它退出之前无法完成任何操作。这会起作用,但不是你想要的方式。如果您的程序的更高版本创建任何其他线程(守护进程,或由其他代码连接),它将中断。另外,
线程是线程。currentThread()
不能保证工作(我认为到目前为止,在任何具有真正线程的平台上,如果在主线程中使用,它总是适用于任何CPython版本,但最好不要假设)。将所有
Thread
对象存储在一个列表中(
threads=[FetchUrl(url)for url in url]
),然后启动它们,然后使用
for threads in threads:Thread.join()
将它们连接起来。同样,对于这样的简单情况,您可以进一步简化它:不必费心创建
Thread
子类,除非您有某种状态要存储,或者有一些API从外部与线程交互,只需编写一个简单的函数,然后执行
threading.Thread(target=my\u Thread\u function,args=[url])
。你的意思是,如果我在同一台机器上同时运行两次相同的脚本,线程中的线程。enumerate():'将包括两次执行的线程吗?请参阅,我认为这与显式线程URL获取程序的实现一样简单。
threading.enumerate()
只包括当前进程中的线程,因此在作为单独进程运行的单独Python实例中运行同一脚本的多个副本不是问题。只是,如果您以后决定扩展此代码(或在其他项目中使用),您可能会在代码的另一部分中创建守护进程线程,或者现在的主代码甚至可能是运行在某个后台线程中的代码。我确信这是“尽可能”简化的,因为这是确保某个聪明人出现并找到进一步简化的方法的最好方法