我惊呆了:python和套接字+线程的奇怪问题

时间:2008-10-20 19:19:46

标签: python multithreading apache sockets

我有一个python脚本是一个http服务器:http://paste2.org/p/89701,当它与ApacheBench(ab)进行基准测试时,其并发级别(-c switch)低于或等于我在中指定的值socket.listen() - 在源代码中调用一切正常,但是只要将并发级别放在apache bench中,就高于socket.listen()中的值 - 调用性能会在地板上掉落,例如:

两次调用之间的代码没有任何变化,我无法弄清楚出了什么问题 - 现在已经有一天出现这个问题了。还要注意:相同代码的多路复用版本(我写的与线程版本进行比较)无论socket设置为什么设置为什么,或者将apache中的并发(-c开关)设置为什么,都可以工作。 / p>

我花了一天时间在IRC / python文档上,发布在comp.lang.python和我的博客上 - 我找不到任何人甚至知道可能出错的地方。帮帮我!

5 个答案:

答案 0 :(得分:7)

我无法确认您的结果,并且您的服务器编码为fishy。我掀起了自己的服务器,也没有这个问题。让我们将讨论推向更简单的层面:

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]

答案 1 :(得分:4)

为此,我还实现了一个异步版本:

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]

答案 2 :(得分:0)

我在tomcat / java的积压上找到了this article,它在积压中提供了一个有趣的见解:

  

例如,如果所有线程都忙   在java处理请求,内核   将处理SYN和TCP握手   直到它的积压已满。当。。。的时候   积压已经满了,它就会掉线   未来的SYN请求。它不会发送   一个RST,即导致“连接被拒绝”   在客户端,而不是客户端   假设包丢失了   重新传输SYN。希望,   积压队列将被清除   然后

正如我所解释的那样,通过要求ab创建更多的同时连接而不是你的 socket配置为处理数据包丢弃,而不是拒绝,我不知道 ab如何处理。可能是它重新发送SYN,但可能在等待之后 一会儿。这可能甚至在某处(TCP协议?)。

如上所述,我不知道,但我希望这暗示了原因。

祝你好运!

答案 3 :(得分:0)

看起来你并没有真正获得并发。显然,当你执行socket.accept()时,主线程不会立即返回等待下一个连接。也许你的连接处理线程只是python代码,所以你要通过SIL(单个解释器锁)进行顺序化。

如果线程之间没有繁重的通信,那么最好使用多进程方案(当然还有预先生成的进程池)

答案 4 :(得分:0)

好的,所以我在一个完全不同的服务器上运行代码 - (我在slicehost获得了一个vps),没有一个问题(一切都按预期工作)所以老实说我认为现在我的笔记本电脑出了问题; p

感谢大家的帮助!