在Python中同时运行多个线程 - 可能吗?
我正在写一个小爬虫,它需要多次获取一个网址,我希望所有的线程能够同时运行。
我写了一段代码来实现这个功能。
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
会更安全。