6

我正在编写一个应该多次获取 URL 的小爬虫,我希望所有线程同时(同时)运行。

我已经写了一小段代码应该做到这一点。

import thread
from urllib2 import Request, urlopen, URLError, HTTPError


def getPAGE(FetchAddress):
    attempts = 0
    while attempts < 2:
        req = Request(FetchAddress, None)
        try:
            response = urlopen(req, timeout = 8) #fetching the url
            print "fetched url %s" % FetchAddress
        except HTTPError, e:
            print 'The server didn\'t do the request.'
            print 'Error code: ', str(e.code) + "  address: " + FetchAddress
            time.sleep(4)
            attempts += 1
        except URLError, e:
            print 'Failed to reach the server.'
            print 'Reason: ', str(e.reason) + "  address: " + FetchAddress
            time.sleep(4)
            attempts += 1
        except Exception, e:
            print 'Something bad happened in gatPAGE.'
            print 'Reason: ', str(e.reason) + "  address: " + FetchAddress
            time.sleep(4)
            attempts += 1
        else:
            try:
                return response.read()
            except:
                "there was an error with response.read()"
                return None
    return None

url = ("http://www.domain.com",)

for i in range(1,50):
    thread.start_new_thread(getPAGE, url)

从 apache 日志来看,线程似乎不是同时运行的,请求之间有一点差距,几乎无法检测到,但我可以看到线程并不是真正并行的。

我读过 GIL,有没有办法绕过它而不调用 C\C++ 代码?我真的不明白 GIL 是如何实现线程化的?python 基本上在下一个线程完成前一个线程后立即解释它?

谢谢。

4

5 回答 5

6

正如您所指出的,GIL 通常会阻止 Python 线程并行运行。

然而,情况并非总是如此。一个例外是 I/O 绑定代码。当一个线程正在等待一个 I/O 请求完成时,它通常会在进入等待之前释放 GIL。这意味着其他线程可以在此期间取得进展。

然而,一般来说,multiprocessing当需要真正的并行性时,这是更安全的选择。

于 2011-09-09T12:58:40.540 回答
1

我读过 GIL,有没有办法绕过它而不调用 C\C++ 代码?

并不真地。通过 ctypes 调用的函数将在这些调用期间释放 GIL。执行阻塞 I/O 的函数也会释放它。还有其他类似的情况,但它们总是涉及到 Python 解释器主循环之外的代码。你不能放弃 Python 代码中的 GIL。

于 2011-09-09T13:09:44.230 回答
1

您可以使用这样的方法来创建所有线程,让它们等待条件对象,然后让它们开始“同时”获取 url:

#!/usr/bin/env python
import threading
import datetime
import urllib2

allgo = threading.Condition()

class ThreadClass(threading.Thread):
    def run(self):
        allgo.acquire()
        allgo.wait()
        allgo.release()
        print "%s at %s\n" % (self.getName(), datetime.datetime.now())
        url = urllib2.urlopen("http://www.ibm.com")

for i in range(50):
    t = ThreadClass()
    t.start()

allgo.acquire()
allgo.notify_all()
allgo.release()

这将使您更接近同时发生所有提取,但是

  • 离开您计算机的网络数据包将按顺序通过以太网线,而不是同时,
  • 即使您的机器上有 16 个以上的内核,您的机器和 Web 主机之间的某些路由器、网桥、调制解调器或其他设备也可能具有较少的内核,并且可能会序列化您的请求,
  • 您从中获取内容的网络服务器将使用accept()调用来响应您的请求。对于正确的行为,这是使用服务器全局锁实现的,以确保只有一个服务器进程/线程响应您的查询。即使您的一些请求同时到达服务器,这也会导致一些序列化。

您可能会使您的请求在更大程度上重叠(即其他请求在某些完成之前开始),但您永远不会让所有请求同时在服务器上启动。

于 2011-09-09T13:37:16.160 回答
0

你还可以看看 pypy 的未来,我们将拥有软件过渡内存(因此取消 GIL)。目前这只是研究和知识分子的嘲笑,但它可能会发展成大事。

于 2011-09-09T13:09:33.060 回答
0

如果您使用 Jython 或 IronPython(将来可能还有 PyPy)运行您的代码,它将并行运行

于 2012-10-06T12:31:03.977 回答