Конечная цель - выполнить метод в фоновом режиме, но не параллельно: когда несколько объектов вызывают этот метод, каждый должен дождаться своей очереди для продолжения. Чтобы добиться работы в фоновом режиме, мне нужно запустить метод в подпроцессе (а не в потоке), и мне нужно запустить его с помощью spawn (а не fork). Чтобы предотвратить параллельное выполнение, очевидным решением является общая глобальная блокировка между процессами.
Когда процессы разветвляются, что является значением по умолчанию в Unix, этого легко добиться, как показано в обоих следующих кодах.
Мы можем поделиться им как переменной класса:
import multiprocessing as mp
from time import sleep
class OneAtATime:
l = mp.Lock()
def f(self):
with self.l:
sleep(1)
print("Hello")
if __name__ == "__main__":
a = OneAtATime()
b = OneAtATime()
p1 = mp.Process(target = a.f)
p2 = mp.Process(target = b.f)
p1.start()
p2.start()
Или мы можем передать его методу:
import multiprocessing as mp
from time import sleep
class OneAtATime:
def f(self, l):
with l:
sleep(1)
print("Hello")
if __name__ == "__main__":
a = OneAtATime()
b = OneAtATime()
m = mp.Manager()
l = mp.Lock()
p1 = mp.Process(target = a.f, args = (l,))
p2 = mp.Process(target = b.f, args = (l,))
p1.start()
p2.start()
Оба этих кода имеют соответствующее поведение печати «привет» с интервалом в одну секунду. Однако при изменении метода запуска на 'spawn', они становятся сломанными.
Первый (1) выводит оба "hello" одновременно. Это связано с тем, что внутреннее состояние класса не обрабатывается, поэтому у них разные блокировки.
Вторая (2) не работает с FileNotFoundError во время выполнения. Я думаю, это связано с тем, что блокировки не могут быть обработаны: см. Python, разделяющий блокировку между процессами.
В этом ответе предлагаются два исправления (примечание: я не могу использовать пул, потому что хочу случайным образом создать произвольное количество процессов).
Я не нашел способа чтобы адаптировать второе исправление, но я попытался реализовать первое:
import multiprocessing as mp
from time import sleep
if __name__ == "__main__":
mp.set_start_method('spawn')
class OneAtATime:
def f(self, l):
with l:
sleep(1)
print("Hello")
if __name__ == "__main__":
a = OneAtATime()
b = OneAtATime()
m = mp.Manager()
l = m.Lock()
p1 = mp.Process(target = a.f, args = (l,))
p2 = mp.Process(target = b.f, args = (l,))
p1.start()
p2.start()
Это не удается с AttributeError и FileNotFoundError (3). Фактически, он также не работает (BrokenPipe) при использовании метода fork (4).
Каков правильный способ разделения блокировки между порожденными процессами?
Краткое объяснение четырех провалы, которые я пронумеровали, тоже было бы неплохо. Я запускаю Python 3.6 под Archlinux.