Ошибка: не удается запустить новый поток

У меня есть сайт, который работает с последующей конфигурацией:

Django + mod-wsgi + apache

В одном из запросов пользователя я отправляю другой HTTP-запрос другой службе и решая это с помощью библиотеки httplib python.

Но иногда эта служба не получает ответа слишком долго, а тайм-аут для 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, ())

И ответ: что происходит?

Спасибо за все, и извините за мой чистый английский.:)

Ответы

Ответ 1

Ошибка "не запускать новый поток" почти наверняка из-за того, что у вас уже слишком много потоков, запущенных в вашем процессе python, и из-за ограниченного ресурса запрос на создание нового потока отказался.

Вероятно, вам следует посмотреть количество тем, которые вы создаете; максимальное число, которое вы сможете создать, будет определяться вашей средой, но оно должно быть как минимум сотен.

Вероятно, было бы неплохо подумать о своей архитектуре; так как это работает асинхронно, возможно, вы можете использовать пул потоков для извлечения ресурсов с другого сайта, а не для запуска потока для каждого запроса.

Еще одно улучшение, которое следует учитывать, - это использование Thread.join и Thread.stop; это, вероятно, было бы лучше достигнуто путем предоставления значения тайм-аута конструктору HTTPSConnection.

Ответ 2

Вы запускаете больше потоков, чем может обрабатывать ваша система. Существует ограничение на количество потоков, которые могут быть активны для одного процесса.

Ваше приложение запускает потоки быстрее, чем потоки выполняются до завершения. Если вам нужно запустить много потоков, вам нужно сделать это более контролируемым образом, я бы предложил использовать пул потоков.

Ответ 3

Я думаю, что лучший способ в вашем случае - установить тайм-аут сокета вместо нерестового потока:

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

Также вы можете установить глобальный таймаут по умолчанию с помощью socket.setdefaulttimeout().

Обновить: см. ответы на Есть ли способ убить тему в Python? (есть несколько довольно информативных), чтобы понять Зачем. Thread.__stop() не завершает поток, а устанавливает внутренний флаг так, чтобы он считался уже остановленным.

Ответ 4

Я полностью переписываю код из 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()

что-то вроде этого.

И я тестирую его сейчас. Спасибо всем вам за помощь.

Ответ 5

Если вы связываете время ожидания, почему бы вам не использовать urllib2.

Ответ 6

в моем случае я добавил этот код перед началом нового потока. Это дает приложению максимальный лимит запущенных потоков, которые будут ждать

while threading.active_count()>150 :
    time.sleep(5)
getting.start()

Примечание: это не очень хорошее решение, но мне пришлось искать обходной путь, чтобы решить этот вопрос, и это сработало для меня.

Ответ 7

Если вы используете ThreadPoolExecutor, проблема может заключаться в том, что ваши max_workers выше потоков, разрешенных вашей ОС.

Кажется, что исполнитель хранит информацию о последних выполненных потоках в таблице процессов, даже если потоки уже выполнены. Это означает, что когда ваше приложение работает в течение длительного времени, в конечном итоге оно зарегистрирует в таблице процессов столько потоков, сколько ThreadPoolExecutor.max_workers