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

Поведение Python time.sleep(0) под linux - вызывает ли это контекстный переключатель?

Этот шаблон появляется много, но я не могу найти прямой ответ.

Некритическая, дружественная программа может выполнять

while(True):
    # do some work

Используя другие технологии и платформы, если вы хотите, чтобы эта программа работала горячо (используйте как можно больше циклов процессора), но будьте вежливы - разрешите другим программам, которые работают, чтобы эффективно замедлить меня, вы часто пишете

while(True):
    #do some work
    time.sleep(0)

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

EDIT: для чего стоили, мы немного экспериментировали в Apple OSX (у них не было удобной линейки Linux). Этот ящик имеет 4 ядра плюс гиперпоточность, поэтому мы создали 8 программ с помощью всего

while(True):
    i += 1

Как и ожидалось, монитор активности показывает каждый из 8 процессов, потребляя более 95% процессора (по-видимому, с четырьмя ядрами и гиперпотоками вы получаете 800% всего). Затем мы создали девятую такую ​​программу. Теперь все 9 пробегают около 85%. Теперь убейте девятого парня и разверните программу с помощью

while(True):
    i += 1
    time.sleep(0)

Я надеялся, что этот процесс будет использоваться близко к 0%, а другой 8 будет работать на 95%. Но вместо этого все девять пробегают около 85%. Так что на Apple OSX сон (0), похоже, не имеет эффекта.

4b9b3361

Ответ 1

Я никогда не думал об этом, поэтому написал этот script:

import time

while True:
    print "loop"
    time.sleep(0.5)

Как тест. Выполнение этого с помощью strace -o isacontextswitch.strace -s512 python test.py дает вам этот вывод в цикле:

write(1, "loop\n", 5)                   = 5
select(0, NULL, NULL, NULL, {0, 500000}) = 0 (Timeout)
write(1, "loop\n", 5)                   = 5
select(0, NULL, NULL, NULL, {0, 500000}) = 0 (Timeout)
write(1, "loop\n", 5)                   = 5
select(0, NULL, NULL, NULL, {0, 500000}) = 0 (Timeout)
write(1, "loop\n", 5)                   = 5
select(0, NULL, NULL, NULL, {0, 500000}) = 0 (Timeout)
write(1, "loop\n", 5)  

select() - системный вызов, так что да, вы переключитесь на контекст (нормально, что при переключении в пространство ядра нет необходимости использовать контекстный переключатель, но если у вас есть другие процессы, то вы говорите здесь, что, если у вас нет данных, готовых для чтения в дескрипторе файла, другие процессы могут выполняться до тех пор) в ядро ​​для этого. Интересно, что задержка заключается в выборе на stdin. Это позволяет python прерывать ваш вход в событиях, таких как ctrl+c, если они пожелают, не дожидаясь, пока код не выйдет из таймаута - что я считаю довольно аккуратным.

Следует отметить, что то же самое относится к time.sleep(0), за исключением того, что переданный параметр времени {0,0}. И эта блокировка спина не идеальна ни для чего, кроме очень коротких задержек - multiprocessing и threads обеспечивают возможность ожидания объектов событий.

Изменить. Итак, я посмотрел, что именно делает Linux. Реализация в do_select (fs\select.c) делает эту проверку:

if (end_time && !end_time->tv_sec && !end_time->tv_nsec) {
    wait = NULL;
timed_out = 1;
}

if (end_time && !timed_out)
    slack = select_estimate_accuracy(end_time);

Другими словами, если предусмотрено время окончания и оба параметра равны нулю (! 0 = 1 и оцениваются как истинные в C), тогда для ожидания установлено значение NULL, и выбор считается тайм-аутом. Однако это не означает, что функция возвращается к вам; он перебирает все дескрипторы файлов, которые у вас есть, и вызывает cond_resched, тем самым потенциально позволяя запускать другой процесс. Другими словами, то, что происходит, полностью зависит от планировщика; если ваш процесс забивает процессорное время по сравнению с другими процессами, возможно, произойдет переключение контекста. Если нет, то задача, в которой вы находитесь (функция do_select ядра), может продолжаться до тех пор, пока она не завершится.

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

Ответ 2

Я думаю, что у вас уже есть ответ от @Ninefingers, но в этом ответе мы попытаемся погрузиться в исходный код python.

Сначала модуль python time реализован на C и для просмотра реализации функции time.sleep вы можете взглянуть на Modules/timemodule.c. Как вы можете видеть (и не получая во всех деталях конкретной платформы), эта функция будет делегировать вызов функции floatsleep.

Теперь floatsleep предназначен для работы на разных платформах, но все же поведение было сочтено похожим, когда это возможно, но поскольку нас интересует только Unix-подобная платформа, откройте только эта часть:

...
Py_BEGIN_ALLOW_THREADS
sleep((int)secs);
Py_END_ALLOW_THREADS

Как вы можете видеть, floatsleep вызывает C sleep и сон man:

Функция sleep() приведет к приостановке вызывающего потока от выполнения до тех пор, пока не будет указано количество секунд в реальном времени по истечении аргумента секунд или...

Но подождите минутку, мы не забыли о GIL?

Хорошо, вот здесь были задействованы макросы Py_BEGIN_ALLOW_THREADS и Py_END_ALLOW_THREADS (проверьте Include/ceval.h, если вас интересует определение эти два макроса), приведенный выше код C может быть переведен с использованием этих двух макросов в:

Save the thread state in a local variable.
Release the global interpreter lock.
... Do some blocking I/O operation ... (call sleep in our case)
Reacquire the global interpreter lock.
Restore the thread state from the local variable.

Более подробную информацию об этом двух макросах можно найти в c-api doc.

Надеюсь, это было полезно.

Ответ 3

В основном вы пытаетесь узурпировать работу планировщика процессора ОС. Скорее всего, было бы намного проще просто вызвать os.nice(100), чтобы сообщить планировщику, что вы очень низкий приоритет, чтобы он мог правильно выполнять свою работу.

Ответ 4

time.sleep(0), похоже, не имеет никакого эффекта, он кажется пропущенным. Предоставление небольшого количества задержки уменьшает загрузку процессора с 100% до 0%:

import time
while True:
    time.sleep(0.0001);

Примечание: True/False заглавные буквы в python.