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

6 投票
5 回答
11283 浏览
提问于 2025-04-17 01:53

我正在写一个小爬虫,它需要多次获取一个网址,我希望所有的线程能够同时运行。

我写了一段代码来实现这个功能。

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 个回答

1

你可以用这样的方式来创建所有的线程,让它们等待一个条件对象,然后让它们开始同时获取网址:

#!/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()调用来回应你的请求。为了确保正确的操作,这个过程会使用一个全局锁,确保只有一个服务器进程或线程来回应你的请求。即使你的某些请求同时到达服务器,这也会导致请求被排队处理。

你可能会让你的请求在某种程度上重叠(也就是说,有的请求在其他请求完成之前就开始了),但你永远无法让所有的请求在服务器上同时开始

1

我读过关于GIL的内容,有没有办法在不调用C或C++代码的情况下绕过它呢?

其实没有。通过ctypes调用的函数在执行时会释放GIL。还有一些进行阻塞输入输出的函数也会释放它。不过类似的情况不多,通常都涉及到主Python解释器循环之外的代码。在你的Python代码里,是无法放开GIL的。

6

正如你所提到的,GIL(全局解释器锁)通常会阻止Python线程同时运行。

不过,这并不是说总是这样。有一种情况例外,那就是I/O密集型代码。当一个线程在等待I/O请求完成时,它通常会在进入等待之前释放GIL。这意味着其他线程在这段时间内可以继续执行。

不过,总的来说,当需要真正的并行处理时,使用multiprocessing会更安全。

撰写回答