Подтвердить что ты не робот

На каких ядрах ядра работают мои процессы Python?

Настройка

Я написал довольно сложную часть программного обеспечения на Python (на ПК с ОС Windows). Мое программное обеспечение запускает в основном две оболочки интерпретатора Python. Первая оболочка запускается (я полагаю), когда вы дважды щелкаете по файлу main.py. Внутри этой оболочки другие потоки запускаются следующим образом:

    # Start TCP_thread
    TCP_thread = threading.Thread(name = 'TCP_loop', target = TCP_loop, args = (TCPsock,))
    TCP_thread.start()

    # Start UDP_thread
    UDP_thread = threading.Thread(name = 'UDP_loop', target = UDP_loop, args = (UDPsock,))
    TCP_thread.start()

Main_thread запускает a TCP_thread и a UDP_thread. Хотя это отдельные потоки, они все работают в одной оболочке Python.

Main_thread также запускает подпроцесс. Это делается следующим образом:

p = subprocess.Popen(['python', mySubprocessPath], shell=True)

Из документации Python я понимаю, что этот подпроцесс работает одновременно (!) в отдельном сеансе/оболочке интерпретатора Python. Main_thread в этом подпроцессе полностью посвящен моему графическому интерфейсу. GUI запускает TCP_thread для всех своих сообщений.

Я знаю, что все становится немного сложнее. Поэтому я обобщил всю настройку на этом рисунке:

введите описание изображения здесь


У меня есть несколько вопросов относительно этой настройки. Я перечислил их здесь:

Вопрос 1 [разрешен]

Правда ли, что интерпретатор Python использует только одно ядро ​​процессора за раз, чтобы запустить все потоки? Другими словами, будет ли Python interpreter session 1 (из рисунка) запускать все 3 потока (Main_thread, TCP_thread и UDP_thread) на одном ядре ЦП?

Ответ: да, это правда. GIL (Global Interpreter Lock) гарантирует, что все потоки будут работать на одном ядре процессора за раз.

Вопрос 2 [еще не решен]

У меня есть способ отслеживать, какое ядро ​​ЦП оно?

Вопрос 3 [Частично решена]

Для этого вопроса мы забываем о потоках, но мы фокусируемся на механизме подпроцесса в Python. Запуск нового подпроцесса подразумевает запуск нового интерпретатора Python экземпляра. Правильно ли это?

Ответ: Да, это правильно. Сначала возникла некоторая путаница в отношении того, будет ли следующий код создавать новый экземпляр интерпретатора Python:

    p = subprocess.Popen(['python', mySubprocessPath], shell = True)

Вопрос выяснен. Этот код действительно запускает новый экземпляр интерпретатора Python.

Будет ли Python достаточно умен, чтобы отдельный экземпляр интерпретатора Python запускался на другом ядре процессора? Есть ли способ отслеживать, какой из них, возможно, с некоторыми спорадическими заявлениями печати?

Вопрос 4 [Новый вопрос]

Дискуссия в сообществе подняла новый вопрос. По-видимому, существует два подхода при создании нового процесса (в новом экземпляре интерпретатора Python):

    # Approach 1(a)
    p = subprocess.Popen(['python', mySubprocessPath], shell = True)

    # Approach 1(b) (J.F. Sebastian)
    p = subprocess.Popen([sys.executable, mySubprocessPath])

    # Approach 2
    p = multiprocessing.Process(target=foo, args=(q,))

Второй подход имеет очевидный недостаток, что он нацелен только на функцию, тогда как мне нужно открыть новый Python script. Во всяком случае, оба подхода похожи на то, что они достигают?

4b9b3361

Ответ 1

Q: Верно ли, что интерпретатор Python использует только одно ядро ​​процессора за раз, чтобы запустить все потоки?

Нет. Совместимость GIL и CPU - это не связанные понятия. GIL может быть выпущен во время блокировки операций ввода-вывода, в то же время длительные вычисления с интенсивным вычислением внутри расширения C.

Если поток заблокирован на GIL; это, вероятно, не на каком-либо ядре процессора, и поэтому справедливо сказать, что чистый многопотоковый код Python может использовать только одно ядро ​​ЦП на этапе реализации CPython.

Q: Другими словами, будет ли сеанс интерпретатора Python 1 (из рисунка) запускать все 3 потока (Main_thread, TCP_thread и UDP_thread) на одном ядре ЦП?

Я не думаю, что CPython неявно управляет аффинностью процессора. Скорее всего, он полагается на планировщик ОС, чтобы выбрать, где запускать поток. Нити Python реализованы поверх реальных потоков ОС.

Q: Или интерпретатор Python способен распространять их по нескольким ядрам?

Чтобы узнать количество используемых ЦП:

>>> import os
>>> len(os.sched_getaffinity(0))
16

Опять же, запланированы ли потоки на разных ЦП, не зависит от интерпретатора Python.

Q: Предположим, что ответ на вопрос 1 - "несколько ядер", есть ли способ отслеживать, на каком ядре работает каждый поток, возможно, с некоторыми спорадическими заявлениями печати? Если ответ на вопрос 1 есть "только одно ядро", есть ли способ отслеживать, какой из них он имеет?

Я предполагаю, что конкретный процессор может меняться от одного временного интервала к другому. Вы могли бы посмотреть что-то вроде /proc/<pid>/task/<tid>/status на старые ядра Linux. На моей машине task_cpu можно прочитать из /proc/<pid>/stat или /proc/<pid>/task/<tid>/stat:

>>> open("/proc/{pid}/stat".format(pid=os.getpid()), 'rb').read().split()[-14]
'4'

Для текущего портативного решения см. psutil предоставляет такую ​​информацию.

Вы можете ограничить текущий процесс набором процессоров:

os.sched_setaffinity(0, {0}) # current process on 0-th core

Q:. Для этого вопроса мы забываем о потоках, но мы фокусируемся на механизме подпроцесса в Python. Запуск нового подпроцесса подразумевает запуск нового сеанса/оболочки интерпретатора Python. Это верно?

Да. subprocess модуль создает новые процессы ОС. Если вы запустите исполняемый файл python, тогда он запустит новый интерпретатор Python. Если вы запустите bash script, то новый интерпретатор Python не будет создан, т.е. Запуск bash исполняемый файл не запустит новый интерпретатор/сеанс Python/etc.

Q: Предположим, что это правильно, будет ли Python достаточно умен, чтобы этот отдельный сеанс интерпретатора выполнялся на другом ядре ЦП? Есть ли способ отслеживать это, возможно, с некоторыми спорадическими заявлениями печати?

См. выше (т.е. ОС решает, где запускать поток, и может существовать OS API, который предоставляет, где выполняется поток).

multiprocessing.Process(target=foo, args=(q,)).start()

multiprocessing.Process также создает новый процесс ОС (который запускает новый интерпретатор Python).

В действительности, мой подпроцесс - это другой файл. Поэтому этот пример не будет работать для меня.

Python использует модули для организации кода. Если ваш код находится в another_file.py, то import another_file в вашем основном модуле и передайте another_file.foo в multiprocessing.Process.

Тем не менее, как бы вы сравнили его с p = subprocess.Popen(..)? Имеет ли значение, если я начинаю новый процесс (или должен сказать "экземпляр интерпретатора python" ) с помощью subprocess.Popen(..) и multiprocessing.Process(..)?

multiprocessing.Process(), вероятно, выполняется поверх subprocess.Popen(). multiprocessing предоставляет API, который похож на API threading и абстрагирует детали связи между процессами python (как объекты Python сериализуются для отправки между процессами).

Если нет задач с интенсивным процессором, вы можете запускать потоки графического интерфейса и ввода-вывода в одном процессе. Если у вас есть серия задач с интенсивным использованием ЦП, то для одновременного использования нескольких процессоров либо используйте несколько потоков с расширениями C, такими как lxml, regex, numpy (или ваш собственный, созданный с помощью Cython), который может выпустить GIL во время длинных вычислений или разгрузить их в отдельные процессы (простым способом является использование пула процессов, например, предоставляемого concurrent.futures).

В:Дискуссия в сообществе подняла новый вопрос. По-видимому, существует два подхода при создании нового процесса (в новом экземпляре интерпретатора Python):

# Approach 1(a)
p = subprocess.Popen(['python', mySubprocessPath], shell = True)

# Approach 1(b) (J.F. Sebastian)
p = subprocess.Popen([sys.executable, mySubprocessPath])

# Approach 2
p = multiprocessing.Process(target=foo, args=(q,))

"Подход 1 (a)" неверен в POSIX (хотя он может работать на Windows). Для переносимости используйте "Подход 1 (b)", если вы не знаете, что вам нужно cmd.exe (в этом случае передайте строку, чтобы убедиться, что используется правильное использование командной строки).

Второй подход имеет очевидный недостаток: он нацелен только на функцию, тогда как мне нужно открыть новый Python script. Во всяком случае, оба подхода похожи на то, что они достигают?

subprocess создает новые процессы, любые процессы, например, вы можете запустить bash script. multprocessing используется для запуска кода Python в другом процессе. Более гибко импортировать модуль Python и выполнять его функцию, чем запускать его как script. См. Вызвать python script с помощью ввода с помощью python script с помощью подпроцесса.

Ответ 2

Поскольку вы используете модуль threading, который создается на thread. Как предполагает документация, в нем используется "реализация потока POSIX" pthread вашей ОС.

  • Нити управляются операционной системой, а не интерпретатором Python. Поэтому ответ будет зависеть от библиотеки pthread в вашей системе. Однако CPython использует GIL для предотвращения одновременного выполнения несколькими потоками байт-кодов Python. Поэтому они будут упорядочены. Но все же они могут быть разделены на разные ядра, что зависит от ваших pthread libs.
  • Просто используйте отладчик и присоедините его к вашему python.exe. Например, команда потока GDB.
  • Как и в вопросе 1, новый процесс управляется вашей ОС и, вероятно, работает на другом ядре. Для его просмотра используйте отладчик или любой монитор процесса. Подробнее см. В документации CreatProcess() страница.

Ответ 3

1, 2: у вас есть три реальных потока, но в CPython они ограничены GIL, поэтому, предполагая, что они работают с чистым питоном, код вы увидите, что использование ЦП используется, как если бы использовалось только одно ядро.

3: Как сказал gdlmx, до OS, чтобы выбрать ядро ​​для запуска потока, но если вам действительно нужен контроль, вы можете настроить слияние процессов или потоков, используя собственный API через ctypes. Поскольку вы находитесь в Windows, это будет так:

# This will run your subprocess on core#0 only
p = subprocess.Popen(['python', mySubprocessPath], shell = True)
cpu_mask = 1
ctypes.windll.kernel32.SetProcessAffinityMask(p._handle, cpu_mask)

Я использую здесь private Popen._handle для упрощения. Чистым способом будет OpenProcess(p.tid) и т.д.

И да, subprocess запускает питон как все остальное в другом новом процессе.