【问题标题】:running multiple threads in python, simultaneously - is it possible?同时在python中运行多个线程-可能吗?
【发布时间】:2011-09-09 12:53:16
【问题描述】:

我正在编写一个应该多次获取 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 基本上是在下一个线程完成前一个线程后立即解释它?

谢谢。

【问题讨论】:

  • 不会 urlopen 阻塞,这意味着线程将并行下载?可能你应该在你自己的服务器上尝试一下,并在服务器端引入一个人为的延迟,比如 3 秒,然后看看线程是否并行开始和结束

标签: python multithreading web-crawler gil


【解决方案1】:

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

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

不过,一般来说,multiprocessing 在需要真正的并行性时是更安全的选择。

【讨论】:

    【解决方案2】:

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

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

    【讨论】:

      【解决方案3】:

      您可以使用这样的方法来创建所有线程,让它们等待条件对象,然后让它们开始获取 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() 调用来响应您的请求。对于正确的行为,这是使用服务器全局锁实现的,以确保只有一个服务器进程/线程响应您的查询。即使您的某些请求同时到达服务器,这也会导致一些序列化。

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

      【讨论】:

        【解决方案4】:

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

        【讨论】:

          【解决方案5】:

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

          【讨论】:

            猜你喜欢
            • 2011-05-03
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多