Самый высокий возвращаемый exec — с таймаутом

#python #multiprocessing #global-variables

#python #многопроцессорная обработка #глобальные переменные

Вопрос:

У меня есть список входных данных для exec, и я хочу знать, какой ввод установит для глобальной переменной наибольшее значение. В настоящее время мой код работает следующим образом:

 s1 = """
global a
a = 1"""

s2 = """
global a
a = 2"""

inputs = [s1, s2]

maxA = 0
for s in inputs:
    exec(s)
    maxA = max([maxA, a])
print(maxA)
 

Который выводит правильный результат.

Проблема в том, что я хочу ограничить время выполнения каждого вызова (скажем, 10 секунд, для этого примера). Метод, который я нашел для этого, использует многопроцессорную обработку, например, следующую:

 import multiprocessing

s1 = """
global a
a = 1"""

s2 = """
global a
a = 2"""

inputs = [s1, s2]

maxA = 0
a = 0

def Execute_For_Multiprocessing(s):
    exec(s)
    global maxA
    maxA = max([maxA, a])
    print(maxA)
    return

for s in inputs:
    p = multiprocessing.Process(target=Execute_For_Multiprocessing, args = [s])
    p.start()
    p.join(10)

    if p.is_alive():
        p.terminate()
        p.join()

print(maxA)
 

Однако это не выводит правильный вывод. Кажется, что в многопроцессорной обработке нет никакого способа изменить глобальные переменные, поэтому, хотя значения вычисляются правильно в Execute_For_Multiprocessing , ни одно из них не сохраняется за его пределами.

У кого-нибудь есть обходной путь для этого? Похоже, что любое из следующих действий решит проблему:

  1. Способ изменения глобальных переменных из многопроцессорного вызова
  2. Метод таймаута для вызовов функций, который НЕ использует многопроцессорную обработку
  3. Альтернативная структура для входных строк, которая позволила бы нам извлекать из них значимые возвращаемые значения.

Комментарии:

1. Я ответил на ваш вопрос, чтобы получить правильный результат, но я понятия не имею, какое отношение имеет время ожидания вызовов функций к вашей проблеме изменения глобальных переменных, чтобы получить правильный результат с помощью подпроцессов. Я действительно не знаю, какова ваша конечная цель, но что бы это ни было, я надеюсь, что ваш опубликованный вами код не соответствует тому, как вы предлагаете его достичь.

Ответ №1:

Проблема в том, что каждый процесс выполняется в своем собственном адресном пространстве и, следовательно, имеет свою собственную копию глобальных переменных, которые он обновляет. На самом деле, если вы работаете на платформе, которая использует fork для создания новых процессов (кстати, вы должны пометить свой вопрос платформой, на которой вы работаете — это важно), каждый вновь созданный процесс начинается с копии адресного пространства, которое было у основного процесса в началевремя создания нового процесса, но когда он изменяет что-либо в этом адресном пространстве, создается новая копия (семантика копирования при записи). Таким образом, любые изменения, внесенные в глобальные переменные вновь созданными процессами, не будут отражены обратно в основной процесс. Однако:

Если вы создаете переменную в общей памяти, используя multiprocessing.Value , например a = multiprocessing.Value('i', 0, lock=False) , then a , это ссылка на значение, которое указывает на местоположение в общей памяти, к которому может получить доступ любой процесс в любом адресном пространстве, и даже если эта ссылка скопирована, она все равно будет действительной:

 import multiprocessing

a = multiprocessing.Value('i', 0, lock=False)
maxA = multiprocessing.Value('i', 0, lock=False)

s1 = """
global a
a.value = 1"""

s2 = """
global a
a.value = 2"""

inputs = [s1, s2]

maxA.value = 0
a.value = 0

def Execute_For_Multiprocessing(s):
    exec(s)
    global maxA
    maxA.value = max(maxA.value, a.value)
    print(maxA.value)
    return

for s in inputs:
    p = multiprocessing.Process(target=Execute_For_Multiprocessing, args = [s])
    p.start()
    p.join()

print(maxA.value)
 

С принтами:

 1
2
2
 

Та же программа, измененная для Windows:

 import multiprocessing

a = multiprocessing.Value('i', 0, lock=False)
maxA = multiprocessing.Value('i', 0, lock=False)

def Execute_For_Multiprocessing(s):
    exec(s)
    global maxA
    maxA.value = max(maxA.value, a.value)
    print(maxA.value)
    return

# required for Windows:

if __name__ == '__main__':

    s1 = """
global a
a.value = 1"""

    s2 = """
global a
a.value = 2"""

    inputs = [s1, s2]

    maxA.value = 0
    a.value = 0

    for s in inputs:
        p = multiprocessing.Process(target=Execute_For_Multiprocessing, args = [s])
        p.start()
        p.join()

    print(maxA.value)
 

С принтами:

 1
2
0
 

Причина, по которой это не работает для Windows, заключается в том, что Windows запускает новые процессы, используя этот spawn метод. Это означает, что новый процесс инициализируется эквивалентом создания нового пустого адресного пространства, а затем запуска нового интерпретатора Python, который инициализирует этот адрес путем повторного чтения в исходной программе и выполнения всех операторов в глобальной области видимости, и только после этого он вызывает указанную вами целевую функцию. Но при этом каждый вновь созданный процесс повторно выполняет инструкции, которые создают multiprocessing.Value экземпляры, и, следовательно, больше не будет ссылаться на те же экземпляры, созданные основным процессом.

Решение состоит в том, чтобы передать подпроцессу значения разделяемой памяти, созданные основным процессом, и заставить подпроцесс инициализировать глобальную память этими значениями:

 import multiprocessing


def Execute_For_Multiprocessing(s, v1, v2):
    global maxA, a

    maxA = v1
    a = v2

    exec(s)
    maxA.value = max(maxA.value, a.value)
    print(maxA.value)
    return

# required for Windows:

if __name__ == '__main__':

    s1 = """
global a
a.value = 1"""

    s2 = """
global a
a.value = 2"""

    inputs = [s1, s2]

    a = multiprocessing.Value('i', 0, lock=False)
    maxA = multiprocessing.Value('i', 0, lock=False)
    # These statements are actually unnecessary:
    #maxA.value = 0
    #a.value = 0

    for s in inputs:
        p = multiprocessing.Process(target=Execute_For_Multiprocessing, args = [s, maxA, a])
        p.start()
        p.join()

    print(maxA.value)
 

С принтами:

 1
2
2
 

Этот код, конечно, также будет работать для платформ, которые используют fork , например, Linux.

Комментарии:

1. Это ответило на ваш вопрос?