错误:无法启动新线程

45
我有一个网站,它的配置如下:
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, ())

答案是:发生了什么?
感谢您所有的帮助,对于我的糟糕英语表示抱歉。 :)
10个回答

46

"无法启动新线程"的错误很可能是由于您已经在Python进程中运行了太多的线程,并且由于某种资源限制,创建新线程的请求被拒绝。

您应该检查您正在创建的线程数量;您能够创建的最大线程数将由您的环境确定,但至少应该达到数百个。

重新考虑这里的架构可能是一个不错的主意;既然这已经在异步地运行,也许您可以使用线程池从另一个站点获取资源,而不是为每个请求始终启动一个线程。

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


23
请注意,可以使用 threading.active_count() 显示正在运行的线程数。 - 101
“几乎肯定”可能是这样,但我遇到了“无法启动新线程”的问题,因为可用内存不足。我限制了可用内存进行测试,以引发MemoryError。不幸的是,所需内存量取决于Python版本,因此我的工作站可以愉快地启动线程,稍后遇到MemoryError,而Jenkins CI则失败并显示“无法启动新线程”。 - peschü

13

您正在启动比系统能够处理的线程更多的线程。一个进程中可以活跃的线程数是有限制的。

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


12

我遇到了类似的情况,但我的进程需要很多线程来处理大量的连接。

我使用以下命令来计算线程数:

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个同时连接。


这将帮助您了解如何使用/etc/security/limits.d/配置文件。设置CentOS/RHEL 5、6、7中的nproc硬限制和软限制值。 - subha.py

6
我认为在你的情况下,最好的方法是设置套接字超时而不是生成线程:
h = httplib.HTTPSConnection(self.config['server'], 
                            timeout=self.config['timeout'])

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

更新:请查看如何在Python中终止线程的几个回答(其中有一些相当信息丰富),以了解原因。 Thread.__stop()不能终止线程,而是设置内部标志,使其被视为已停止。


这对我可能会很有用。谢谢。 - Oduvan

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()

我现在正在测试类似的东西。感谢大家的帮助。


4

如果您想设置超时时间,为什么不使用urllib2呢。


urllib2没有连接超时。 - Oduvan
1
urllib2确实有超时功能。 <snip> urllib2.urlopen(url[, data][, timeout])</snip> - piyer
1
timeout参数是Python 2.6中的新功能。 - Denis Otkidach

4

我发现这个问题是因为在Docker容器内安装软件包时,pip失败了。pip仓库上的相关问题建议这是由于rich抛出的一个措辞不当的异常,原因是系统达到了最大线程数限制。以下修复方法提供:

  • 升级Docker到版本> 20.10.7
  • 使用-q选项运行pip以抑制rich的输出

在尝试按照 Docker 入门教程中的 catnip 示例时,我在 Ubuntu 20.04 上添加了 -q,这对我起到了作用。然后,这个示例并没有成功运行,可能是因为要求设置得不好。 - undefined

3
我在本地运行Python脚本,仅用于将某些文件从一种格式复制并转换为另一种格式,我希望最大化运行线程的数量,以便尽快完成。
请注意:如果您不是在特定机器上使用它进行快速脚本,则从架构角度来看,这不是一个好的解决方法。
在我的情况下,我检查了我的机器可以运行的最大线程数,直到出现错误,这个数字是150。
我在启动新线程之前添加了以下代码。该代码会检查是否达到了运行线程的最大限制,如果达到了,则应用程序将等待一段时间,直到某些运行中的线程完成,然后才会启动新的线程。
while threading.active_count()>150 :
    time.sleep(5)
mythread.start()

你是如何检查和发现这个150的值的? - undefined

1
如果您正在使用ThreadPoolExecutor,则问题可能是您的max_workers比操作系统允许的线程数还要高。
似乎执行器将上一次执行的线程信息保留在进程表中,即使线程已经完成。这意味着当您的应用程序运行了很长时间时,最终它将在进程表中注册与ThreadPoolExecutor.max_workers相同数量的线程。

解决方案是什么? - undefined

0
据我所知,这不是Python的问题。你的系统无法创建另一个线程(我遇到过同样的问题,无法通过ssh在另一个cli上启动htop)。
Fernando Ulisses dos Santos的答案非常好。我只想补充一下,还有其他工具可以从“外部”限制进程数量和内存使用情况。这在虚拟服务器中非常常见。起点是你供应商的界面,或者你可能会有运气在像/proc/user_beancounters这样的文件中找到一些信息。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接