我震惊了:Python、套接字和线程的奇怪问题
我有一个用Python写的HTTP服务器脚本:http://paste2.org/p/89701。我用ApacheBench(ab)来测试它的性能。当我设置的并发级别(-c选项)小于或等于我在代码中socket.listen()调用时指定的值,一切都运行得很好。但是,一旦我把ApacheBench中的并发级别设置得比socket.listen()中的值高,性能就会大幅下降。以下是一些例子:
- 当socket.listen(10),并且用ab -n 50 -c 10 http://localhost/时,性能是1200请求/秒
- 当socket.listen(10),并且用ab -n 50 -c 11 http://localhost/时,性能降到40请求/秒
- 当socket.listen(100),并且用ab -n 5000 -c 100 http://localhost/时,性能是1000请求/秒
- 当socket.listen(100),并且用ab -n 5000 -c 101 http://localhost/时,性能降到32请求/秒
在这两个调用之间,代码没有任何变化,我搞不清楚问题出在哪里,已经研究这个问题一天了。另外要注意的是:同样代码的多路复用版本(我写这个是为了和线程版本进行比较)无论socket.listen()设置成什么值,或者Apache的并发(-c选项)设置成什么值,运行得都很好。
我在IRC和Python文档上花了一天的时间,也在comp.lang.python和我的博客上发帖,但找不到任何人能告诉我可能出什么问题。帮帮我吧!
5 个回答
我找到了一篇关于Tomcat和Java的文章,里面提到了一些关于请求处理的有趣内容:
比如说,如果Java中的所有线程都在忙着处理请求,操作系统的内核会处理SYN和TCP的握手,直到它的请求队列满了。当队列满了,它就会直接丢掉后续的SYN请求。它不会发送一个重置连接的信号,也就是不会让客户端看到“连接被拒绝”的提示。相反,客户端会认为这个数据包丢失了,然后重新发送SYN请求。希望到那时候,请求队列能清空一些。
我理解的是,如果你用ab工具创建的同时连接数超过了你的套接字能处理的数量,数据包就会被丢掉,而不是被拒绝。我不太清楚ab是怎么处理这种情况的。可能它会重新发送SYN请求,但可能会等一段时间再发送。这可能在某个地方有说明(比如TCP协议?)。
总之,我不太确定,但希望这些信息能帮助你找到问题的原因。
祝你好运!
为了好玩,我还实现了一个异步版本:
import socket, Queue, select
class Request(object):
def __init__(self, conn):
self.conn = conn
self.fileno = conn.fileno
self.perform = self._perform().next
def _perform(self):
data = self.conn.recv(4048)
while '\r\n\r\n' not in data:
msg = self.conn.recv(4048)
if msg:
data += msg
yield
else:
break
reading.remove(self)
writing.append(self)
data = 'HTTP/1.1 200 OK\r\n\r\nHello World'
while data:
sent = self.conn.send(data)
data = data[sent:]
yield
writing.remove(self)
self.conn.close()
class Acceptor:
def __init__(self):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('', 1234))
sock.listen(10)
self.sock = sock
self.fileno = sock.fileno
def perform(self):
conn, addr = self.sock.accept()
reading.append(Request(conn))
if __name__ == '__main__':
reading = [Acceptor()]
writing = list()
while 1:
readable, writable, error = select.select(reading, writing, [])
for action in readable + writable:
try: action.perform()
except StopIteration: pass
这个版本执行了:
ab -n 10000 -c 10 http://127.0.0.1:1234/ --> 16822.13 [#/sec]
ab -n 10000 -c 11 http://127.0.0.1:1234/ --> 15704.41 [#/sec]
我无法确认你的结果,而且你的服务器代码看起来有点问题。我自己搭建了一个服务器,也没有遇到这个问题。我们来把讨论简化一下:
import thread, socket, Queue
connections = Queue.Queue()
num_threads = 10
backlog = 10
def request():
while 1:
conn = connections.get()
data = ''
while '\r\n\r\n' not in data:
data += conn.recv(4048)
conn.sendall('HTTP/1.1 200 OK\r\n\r\nHello World')
conn.close()
if __name__ == '__main__':
for _ in range(num_threads):
thread.start_new_thread(request, ())
acceptor = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
acceptor.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
acceptor.bind(('', 1234))
acceptor.listen(backlog)
while 1:
conn, addr = acceptor.accept()
connections.put(conn)
在我的机器上运行的结果是:
ab -n 10000 -c 10 http://127.0.0.1:1234/ --> 8695.03 [#/sec]
ab -n 10000 -c 11 http://127.0.0.1:1234/ --> 8529.41 [#/sec]