Отслеживание висящих тем в питоне
У меня есть приложение на основе Python 3.7.2 Asyncio. Существует конечная точка, предоставляющая некоторую информацию о потоке:
threads_info = {}
for thread in enumerate():
threads_info[thread.__str__()] = traceback.format_stack(sys._current_frames()[thread.ident])
Насколько я знаю, не должно быть никаких потоков, кроме основного потока, однако, когда я запрашиваю конечную точку, я вижу этот странный ThreadPoolExecutor. Начинается только с одного работника и продолжает увеличиваться:
Любые идеи, почему, как и что это за ThreadPoolExecutor? Возможно, есть какой-то способ увидеть, где в коде он создан или какой пакет его создает?
Dockerfile, который я использую для запуска своего приложения:
FROM python:3.7.2-alpine as base
FROM base as builder
RUN mkdir /install
WORKDIR /install
COPY requirements /requirements
RUN apk add \
"gcc>8.2.0" \
"g++>8.2.0" \
"libffi-dev>3.2.1" \
"musl-dev>1.1.20"
RUN pip install --install-option="--prefix=/install" -r /requirements
FROM base
RUN apk add --no-cache procps
COPY --from=builder /install /usr/local
COPY src /app
WORKDIR /app
RUN mkdir logs
ENTRYPOINT ["python", "-u", "app.py"]
EXPOSE 80/tcp
Мой файл требований:
quart==0.8.1
aiohttp==3.5.4
cchardet==2.1.4
aiodns==1.2.0
requests==2.21.0
psutil==5.6.1
2 ответа
Возможно, есть какой-то способ увидеть, где в коде он создан или какой пакет его создает?
Да, как упоминалось в предыдущем ответе, это был исполнитель по умолчанию asyncio. Чтобы отладить, какой пакет является виновником, мне пришлось написать моего исполнителя:
class AsyncioDefaultExecutor(ThreadPoolExecutor):
def __init__(self, thread_name_prefix='', max_workers=None):
self.logger = get_logger("asyncioTh")
super(AsyncioDefaultExecutor, self).__init__(thread_name_prefix=thread_name_prefix)
def submit(self, fn, *args, **kwargs):
debug_info = "Function " + fn.__name__ + " in " + fn.__code__.co_filename + ":" + \
str(fn.__code__.co_firstlineno) + "\n" + "".join(traceback.format_stack())
self.logger.info(debug_info)
return super(AsyncioDefaultExecutor, self).submit(fn, *args, **kwargs)
и установите его как исполнителя по умолчанию:
loop.set_default_executor(AsyncioDefaultExecutor())
Это приводило к приятной трассировке каждый раз, когда передается новое задание.
Любые идеи, почему, как и что это за ThreadPoolExecutor?
ThreadPoolExecutor
реализация пула потоков, предоставляемая concurrent.futures
модуль. Он используется для асинхронного выполнения синхронного кода путем передачи его в отдельный поток. Цель пула - избежать задержек создания и присоединения потока для каждой отдельной задачи; вместо этого пул создает рабочий поток только один раз и сохраняет его в пуле для последующего использования. Максимальное количество потоков в пуле может быть настроено и по умолчанию количество ядер умножено на 5.
Потоки, которые вы видите в своем коде, принадлежат ThreadPoolExecutor
создается одной из библиотек, которые вы используете. В частности, Asyncio создает исполнителя для использования run_in_executor
метод. Этот исполнитель используется самой asyncio для предоставления асинхронного интерфейса для вызовов, которые изначально не имеют такой, например, для разрешения DNS, предоставляемого операционной системой.
В общем, при использовании нетривиальных сторонних библиотек вы не можете предполагать, что ваш код будет единственным, создающим потоки. Итерируя по живым потокам, вы просто игнорируете те, которые не создавали, что можно сделать, например, пометив созданные потоки с помощью специального атрибута на Thread
объект.