Является ли ограничение максимального потока фактической проблемой для Python/Linux?

В текущем приложении Python, над которым я работаю, необходимо использовать 1000+ потоков (Pingons threading module). Не то, чтобы какой-либо один поток работал в циклах max cpu, это просто приложение загрузки нагрузки веб-сервера, которое я создаю. И.Е. эмулировать 200 клиентов firefox, все тоскующие по веб-серверу и загружающие небольшие веб-компоненты, в основном имитирующие людей, которые работают в считанные секунды, а не микросекунды.

Итак, я читал различные темы, такие как "сколько потоков поддерживает python в Linux/windows и т.д., и я видел множество разнообразных ответов. Один из пользователей сказал по умолчанию о памяти и ядре Linux только выделяет 8Meg для потоков, если он превышает это, тогда потоки начинают убивать ядро.

Один парень заявил, что это не проблема для CPython, потому что в любом случае работает только один поток (из-за GIL), поэтому мы можем указать gazillion threads??? Какая истинная правда в этом?

Ответ 1

  • "Один поток работает одновременно из-за GIL". Ну, вроде. GIL означает, что только один поток может выполнять код Python за раз. Однако любое количество потоков может выполнять IO, различные другие системные вызовы или другой код, который не содержит GIL.

    Похоже, что ваши потоки будут выполнять в основном сетевой ввод-вывод, и любое количество потоков может одновременно выполнять операции ввода-вывода. Конкурс GIL может быть довольно жестким с 1000 потоками, но вы всегда можете создавать несколько процессов Python и делиться потоками ввода-вывода между ними (т.е. fork пару раз, прежде чем вы начнете).

  • "Ядро Linux по умолчанию только выделяет 8Meg для потоков". Я не знаю, откуда вы это слышали. Возможно, что вы на самом деле слышали: "В Linux размер стека по умолчанию часто равен 8 MiB", что верно. Каждый поток будет использовать 8 Мбайт адресного пространства для стека (без проблем в 64-разрядной версии), а также ресурсы ядра для дополнительных карт памяти и самого процесса потока. Вы можете изменить размер стека, используя библиотечную функцию threading.stack_size, которая помогает, если у вас много потоков, которые не делают глубоких вызовов.

    >>> import threading
    >>> threading.stack_size()
    0 # platform default, probably 8 MiB
    >>> threading.stack_size(64*1024) # 64 KiB stack size for future threads
    
  • Другие в этой теме предложили использовать асинхронную/неблокирующую среду. Ну, ты можешь это сделать. Однако в современном ядре Linux многопоточная модель конкурирует с асинхронными (select/poll/epoll) методами мультиплексирования ввода-вывода. Перезапись вашего кода для использования асинхронной модели - это нетривиальная работа, поэтому я бы сделал это только в том случае, если бы не смог получить требуемую производительность из поточной модели. Если ваши потоки действительно пытаются имитировать человеческую задержку (например, проводят большую часть времени), существует множество сценариев, в которых асинхронный подход на самом деле медленнее. Я не уверен, применим ли это к Python, где только сокращение GIL-конкуренции может оказаться полезным для коммутатора.

Ответ 2

Оба из них отчасти верны:

  • В каждом потоке есть стек, и вы можете исчерпать адресное пространство для стека, если вы создаете достаточно потоков.

  • Python также имеет что-то, называемое GIL, которое позволяет только одному потоку Python запускаться за раз. Однако, как только код Python вызывает код C, этот код C может запускаться, пока выполняется поток Python. Тем не менее, потоки в Python по-прежнему являются физическими, и все еще существует ограничение пространства стека.

Если вы планируете иметь много соединений, а не использовать много потоков, рассмотрите возможность использования асинхронного дизайна. Twisted, вероятно, будет хорошо работать здесь.