Взаимодействие с ОС и процессы
Кроссплатформенность, POSIX и Python.
Модули os и sys и subprocess
os — аналоги libc-вызовов
sys — argv, stdin/..., path (PYTHONPATH), executable, …
не используйте os.system — некроссплатформенно
run() сильно мощнее и не сложнее
>>> res = subprocess.run(["ls", "-s", "/tmp"]) итого 4 0 sddm-:0-HpeJaS 0 sddm-auth548d2226-4e14-4649-8def-af1477a0c991 0 sddm-auth55b1d4e0-f16e-4306-8057-cda4f7479de2 0 systemd-private-84e1015dd3a940ed8b01a8e640e11fae-colord.service-BXQAJg 0 systemd-private-84e1015dd3a940ed8b01a8e640e11fae-ModemManager.service-73Y0Ai 0 systemd-private-84e1015dd3a940ed8b01a8e640e11fae-rtkit-daemon.service-LTU82i 0 systemd-private-84e1015dd3a940ed8b01a8e640e11fae-systemd-logind.service-7iTjZg 0 systemd-private-84e1015dd3a940ed8b01a8e640e11fae-upower.service-sIm7Tg 0 VMwareDnD 4 wlog >>> res = subprocess.run(["ls", "---s", "/tmp"], capture_output=True) >>> res.stdout b'' >>> print(res.stderr.decode()) ls: нераспознанный параметр «---s» По команде «ls --help» можно получить дополнительную информацию.
- Взаимодействие через канал:
>>> from subprocess import * >>> p1 = Popen(["ls", "-s", "/tmp"], stdout=PIPE) >>> p2 = Popen(["grep", "private"], stdin=p1.stdout, stdout=PIPE) >>> output, outerr = p2.communicate() >>> print(output.decode()) 0 systemd-private-84e1015dd3a940ed8b01a8e640e11fae-colord.service-BXQAJg 0 systemd-private-84e1015dd3a940ed8b01a8e640e11fae-ModemManager.service-73Y0Ai 0 systemd-private-84e1015dd3a940ed8b01a8e640e11fae-rtkit-daemon.service-LTU82i 0 systemd-private-84e1015dd3a940ed8b01a8e640e11fae-systemd-logind.service-7iTjZg 0 systemd-private-84e1015dd3a940ed8b01a8e640e11fae-upower.service-sIm7Tg >>> print(outerr) None
Треды
В Python треды работают синхронно (читай: не работают) по причине GIL (глобальной блокировки интерпретатора)
- GIL придумал Гвидо в прошлом тысячелетии
- GIL позволяет очень эффективно работать с памятью в однопоточном режиме
- GIL простой как палка
- GIL не мешает пользоваться многопроцессностью. В случае Python различия в производительности невелики.
Модуль threading можно использовать, если:
- Все треды, кроме одного, часто спят на вводе-выводе
- Дизайн алгоритма требует частого использования общих объектов
Многопроцессность
Кроссплатформенный модуль multiprocessing
fork() как основной инструмент
Представление функции как единицы распараллеливания
multiprocessing — описатель процесса-функции
процесс.start() — запустить дочерний процесс
процесс.join() — дождаться завершения дочернего процесса
- Обмен данными с помощью «каналов» (больше похожи на сокеты, потому что двунаправленные!)
Дыра1, Дыра2 = Pipe() — два отверстия трубы
ДыраA.send() — записать данные в один конец трубы (A=1,2)
ДыраB.recv() — считать данные на другом конце (B=2,1)
Дыра.poll() — есть ли данные для чтения
- Другой обмен данными:
multiprocessing Ещё больше похожи на сокеты, в т. ч. по сети
multiprocessing.shared_memory — разделяемая память по-питоньи)
- и т. п.
Простой диспетчер задач multiprocessing — Pool
- Формируется последовательность обработчиков (можно один) и последовательность входных данных для них
- Диспетчер запускает N процессов в параллель,
1 import multiprocessing 2 import time 3 import sys 4 import random 5 6 N = 7 if len(sys.argv)<2 else int(sys.argv[1]) 7 8 def proc(args): 9 number, num = args 10 print(f"{num}: {number}") 11 time.sleep(number) 12 return number 13 14 pool = multiprocessing.Pool(10) 15 Data = [(random.randint(1,5), i) for i in range(N)] 16 17 print(*Data) 18 Res = pool.map(proc, Data) 19 print(*Res) 20 pool.close()
- Можно поиграть числами и померить, что размер пула, больший, чем количество ядер на процессоре, особого смысла не имеет
Важно вызвать pool.close(), потому что это не входит в процедуру удаления объекта, и диспетчер может продолжать хотеть управлять ещё живыми дочерними процессами
Вообще, multiprocessing — универсальная таблетка для увеличения в N раз быстродействия и ресурсопотребления программы, которая не менее, чем N раз вызывает какие-то функции, проводя в них значительное время, где N — размер пула.