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

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

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

while(True):
    # do some work

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

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

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

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

while(True):
    i += 1

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

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

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

29
задан Matthew Lund 1 September 2011 в 18:05
поделиться

3 ответа

Я никогда не думал об этом, поэтому я написал такой сценарий:

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), может просто выполняться, пока не будет выполнена.

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

24
ответ дан 28 November 2019 в 01:44
поделиться

Я думаю, что у вас уже есть ответ от @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 сон и из страницы руководства по сну :

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

Но подождите, разве мы не забыли о 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 .

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

12
ответ дан 28 November 2019 в 01:44
поделиться

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

8
ответ дан 28 November 2019 в 01:44
поделиться
Другие вопросы по тегам:

Похожие вопросы: