35

我有一个使用以下配置运行的站点:

Django + mod-wsgi + apache

在一个用户的请求中,我向另一个服务发送了另一个 HTTP 请求,并通过 python 的 httplib 库解决了这个问题。

但有时这个服务没有得到答案太久,并且 httplib 的超时不起作用。所以我创建了线程,在这个线程中我向服务发送请求,并在 20 秒后加入它(20 秒 - 是请求超时)。这是它的工作原理:

class HttpGetTimeOut(threading.Thread):
    def __init__(self,**kwargs):
        self.config = kwargs
        self.resp_data = None
        self.exception = None
        super(HttpGetTimeOut,self).__init__()
    def run(self):

        h = httplib.HTTPSConnection(self.config['server'])
        h.connect()
        sended_data = self.config['sended_data']
        h.putrequest("POST", self.config['path'])
        h.putheader("Content-Length", str(len(sended_data)))
        h.putheader("Content-Type", 'text/xml; charset="utf-8"')
        if 'base_auth' in self.config:
            base64string = base64.encodestring('%s:%s' % self.config['base_auth'])[:-1]
            h.putheader("Authorization", "Basic %s" % base64string)
        h.endheaders()

        try:
            h.send(sended_data)
            self.resp_data = h.getresponse()
        except httplib.HTTPException,e:
            self.exception = e
        except Exception,e:
            self.exception = e

像这样的东西......

并通过此功能使用它:

getting = HttpGetTimeOut(**req_config)
getting.start()
getting.join(COOPERATION_TIMEOUT)
if getting.isAlive(): #maybe need some block
    getting._Thread__stop()
    raise ValueError('Timeout')
else:
    if getting.resp_data:
        r = getting.resp_data
    else:
        if getting.exception:
            raise ValueError('REquest Exception')
        else:
            raise ValueError('Undefined exception')

一切正常,但有时我开始捕捉到这个异常:

error: can't start new thread

在开始新线程的行:

getting.start()

回溯的下一行也是最后一行是

File "/usr/lib/python2.5/threading.py", line 440, in start
    _start_new_thread(self.__bootstrap, ())

答案是:发生了什么?

谢谢大家,对不起我纯正的英语。:)

4

8 回答 8

41

“无法启动新线程”错误几乎可以肯定是因为您的 python 进程中已经运行了太多线程,并且由于某种资源限制,创建新线程的请求被拒绝。

您可能应该查看正在创建的线程数;您可以创建的最大数量将取决于您的环境,但至少应为数百个。

在这里重新考虑您的架构可能是一个好主意;无论如何,这是异步运行的,也许您可​​以使用线程池从另一个站点获取资源,而不是总是为每个请求启动一个线程。

另一个需要考虑的改进是使用 Thread.join 和 Thread.stop;通过向 HTTPSConnection 的构造函数提供超时值可能会更好地实现这一点。

于 2009-12-02T18:56:42.117 回答
12

您启动的线程数超出了系统的处理能力。一个进程可以激活的线程数是有限制的。

您的应用程序启动线程的速度比线程运行完成的速度要快。如果您需要启动许多线程,则需要以更可控的方式进行,我建议您使用线程池。

于 2009-12-02T18:56:01.170 回答
6

我认为在你的情况下最好的方法是设置套接字超时而不是产生线程:

h = httplib.HTTPSConnection(self.config['server'], 
                            timeout=self.config['timeout'])

您还可以使用socket.setdefaulttimeout()函数设置全局默认超时。

更新:查看Is there any way to kill a Thread in Python?问题(有几个相当翔实的)来理解为什么。Thread.__stop()不会终止线程,而是设置内部标志,以便它被认为已经停止。

于 2009-12-03T11:03:21.510 回答
6

我在类似的情况下运行,但我的进程需要运行大量线程来处理大量连接。

我用命令计算了线程数:

ps -fLu user | wc -l

它显示 4098。

我切换到用户并查看系统限制:

sudo -u myuser -s /bin/bash

ulimit -u

得到 4096 作为响应。

因此,我编辑了 /etc/security/limits.d/30-myuser.conf 并添加了以下行:

myuser hard nproc 16384

myuser soft nproc 16384

重新启动服务,现在它使用 7017 个线程运行。

附言。我有一个 32 核的服务器,我正在使用这种配置处理 18k 的同时连接。

于 2020-11-27T01:17:23.833 回答
5

我将代码从 httplib 完全重写为 pycurl。

c = pycurl.Curl()
c.setopt(pycurl.FOLLOWLOCATION, 1)
c.setopt(pycurl.MAXREDIRS, 5)
c.setopt(pycurl.CONNECTTIMEOUT, CONNECTION_TIMEOUT)
c.setopt(pycurl.TIMEOUT, COOPERATION_TIMEOUT)
c.setopt(pycurl.NOSIGNAL, 1)
c.setopt(pycurl.POST, 1)
c.setopt(pycurl.SSL_VERIFYHOST, 0)
c.setopt(pycurl.SSL_VERIFYPEER, 0)
c.setopt(pycurl.URL, "https://"+server+path)
c.setopt(pycurl.POSTFIELDS,sended_data)

b = StringIO.StringIO()
c.setopt(pycurl.WRITEFUNCTION, b.write)

c.perform()

类似的东西。

我现在测试它。谢谢大家的帮助。

于 2009-12-03T18:46:52.053 回答
4

如果您要设置超时,为什么不使用urllib2

于 2009-12-03T15:57:48.597 回答
2

我在我的机器上运行 python 脚本只是为了复制一些文件并将其从一种格式转换为另一种格式,我想最大化运行线程的数量以尽快完成。

注意:从体系结构的角度来看,这不是一个好的解决方法如果您没有将它用于特定机器上的快速脚本。

在我的情况下,我检查了我的机器在出现错误之前可以运行的最大运行线程数,它是150

我在开始一个新线程之前添加了这段代码。检查是否达到运行线程的最大限制,然后应用程序将等待某些运行线程完成,然后它将启动新线程

while threading.active_count()>150 :
    time.sleep(5)
mythread.start()
于 2019-03-03T12:34:27.723 回答
1

如果您使用的是 ThreadPoolExecutor,问题可能是您的 max_workers 高于操作系统允许的线程数。

执行器似乎将最后执行的线程的信息保留在进程表中,即使线程已经完成。这意味着当你的应用程序运行了很长时间后,最终它会在进程表中注册与 ThreadPoolExecutor.max_workers 一样多的线程

于 2019-03-22T15:37:41.893 回答