同时在python中运行多个线程 - 这可能吗?

时间:2011-09-09 12:53:16

标签: python multithreading web-crawler gil

我正在写一个应该多次获取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基本上会在完成前一个线程时解释下一个线程吗?

感谢。

5 个答案:

答案 0 :(得分:4)

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

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

但是,一般情况下,当需要真正的并行性时,multiprocessing是更安全的选择。

答案 1 :(得分:1)

  

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

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

答案 2 :(得分: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个以上的核心,您的计算机和网络主机之间的某些路由器,网桥,调制解调器或其他设备也可能拥有较少的核心,并且可能会将您的请求序列化,
  • 您从中获取内容的网络服务器将使用accept()来回复您的请求。对于正确的行为,使用服务器全局锁实现,以确保只有一个服务器进程/线程响应您的查询。即使您的某些请求同时到达服务器,也会导致一些序列化。

您可能会在更大程度上接受重叠的请求(即其他人在完成之前开始),但您永远不会得到所有同时启动的请求< / em>在服务器上。

答案 3 :(得分:0)

你也可以看看像pypy的未来,我们将拥有软件过渡记忆(从而废除GIL)这一切只是研究和知识分子嘲笑,但它可能会变成一个大的东西。

答案 4 :(得分:0)

如果您使用Jython或IronPython(以及未来的PyPy)运行代码,它将并行运行