Приоритет планирования каждого потока в Linux с политикой планирования по умолчанию (SCHED_OTHER)

Я пытаюсь реализовать кросс-платформенную оболочку для настройки приоритета текущего потока; в POSIX «правильная» вещь выглядит примерно так:

/// set the priority adjustment for the current thread
/// @param pri  priority between -2 and 2; 0 is the "normal" priority
/// @return true if successful
bool czu::set_thread_priority(int pri) {
    struct sched_param sp;
    int policy;
    pthread_t self = pthread_self();
    if (pthread_getschedparam(self, &policy, &sp) != 0) return false;
    int pmin = sched_get_priority_min(policy);
    int pmax = sched_get_priority_max(policy);
    if (pmin == -1 || pmax == -1) return false;
    sp.sched_priority = pmin + (pmax-pmin) * (pri + 2) / 4;
    return pthread_setschedparam(self, policy, &sp) == 0;
}

Это работает так, как я и ожидал в macOS (где потоки, кажется, имеют диапазон приоритетов [15, 47], по умолчанию 31), но совсем не работает в Linux, как в случае с политикой по умолчанию (0 AKA SCHED_OTHER), диапазон разрешенных приоритетов равно [0, 0], т. е. корректировка приоритетов невозможна.

Вы можете переключить политику планирования на что-то вроде SCHED_FIFO или SCHED_RR и использовать их значения приоритета, но это совсем не то, что я хочу (из man 7 sched они всегда имеют приоритет над SCHED_OTHER задачами, и в целом они подходят для очень специфических сценариев).

Вместо этого я обнаружил, что работает использование значения nice (через nice(2)/setpriority(2)), которое, вопреки тому, что говорит POSIX, зависит от потока, а не от процесса (кажется, довольно прямым следствием очень размытой линии между потоками и процессами в Linux, причем в конечном итоге все это просто «задача» с различными возможными общими вещами с другими задачами). В итоге у меня получилось:

bool czu::set_thread_priority(int pri) {
    if (pri < -2) pri = -2;
    if (pri > 2) pri = 2;
#ifdef _WIN32
    // %pri was conveniently chosen so that it matches SetThreadPriority
    return SetThreadPriority(GetCurrentThread(), pri);
#elif __linux__
    // On Linux the pthread_setschedparam stuff works only with particular
    // scheduling policies, specifically it does not work with the default one;
    // instead, nice(2)/setpriority(2) works, and affects only the current
    // thread instead of doing what POSIX would say (whole process).
    // Here the allowed range is [20, -19] (20 = minimum priority); we can
    // ignore clamping -20 as setpriority is already guaranteed to do it.
    return setpriority(PRIO_PROCESS, 0, pri*-10) == 0;
#else
    struct sched_param sp;
    int policy;
    pthread_t self = pthread_self();
    if (pthread_getschedparam(self, &policy, &sp) != 0) return false;
    int pmin = sched_get_priority_min(policy);
    int pmax = sched_get_priority_max(policy);
    if (pmin == -1 || pmax == -1) return false;
    sp.sched_priority = pmin + (pmax-pmin) * (pri + 2) / 4;
    return pthread_setschedparam(self, policy, &sp)) == 0;
#endif
}

это кажется достаточно хорошим, но потом я заметил в man 2 setpriority:

Согласно POSIX, значение nice — это настройка для каждого процесса. Однако в текущей реализации потоков POSIX в Linux/NPTL значение nice является атрибутом каждого потока: разные потоки в одном процессе могут иметь разные значения nice. Переносимым приложениям не следует полагаться на поведение Linux, которое в будущем может быть приведено в соответствие со стандартами.

Итак, если я хочу быть ориентированным на будущее, но при этом иметь возможность регулировать приоритет потоков с помощью SCHED_OTHER политики планирования по умолчанию, что мне следует делать?

На ум приходит возможность работать в резервном режиме: запустите танец sched_get_priority_min/sched_get_priority_max, и если окажется, что это [0,0] И мы находимся в Linux, предположим, что мы находимся в ситуации, когда хорошее значение соответствует поток (идея состоит в том, что если значение nice когда-либо создается для каждого потока, материал pthreads должен начать работать должным образом, с диапазоном пригодных значений).

bool czu::set_thread_priority(int pri) {
    if (pri < -2) pri = -2;
    if (pri > 2) pri = 2;
#ifdef _WIN32
    // %pri was conveniently chosen so that it matches SetThreadPriority
    return SetThreadPriority(GetCurrentThread(), pri);
#else
    struct sched_param sp;
    int policy;
    pthread_t self = pthread_self();
    // on POSIX the admissible priorities range isn't fixed, and depends from
    // the scheduler policy for the thread; let's figure all this out
    if (pthread_getschedparam(self, &policy, &sp) != 0) return false;
    int pmin = sched_get_priority_min(policy);
    int pmax = sched_get_priority_max(policy);
    if (pmin == -1 || pmax == -1) return false;
#ifdef __linux__
    if (pmax - pmin == 0) {
        // On Linux the pthread_setschedparam stuff works only with particular
        // scheduling policies, specifically it does not work with the default
        // one; instead, nice(2)/setpriority(2) works, and affects only the
        // current thread instead of doing what POSIX would say (whole process).
        // Here the allowed range is [20, -19] (20 = minimum priority); we can
        // ignore clamping -20 as setpriority is already guaranteed to do it.
        return setpriority(PRIO_PROCESS, 0, pri*-10) == 0;
    }
#endif
    // plain linear interpolation
    sp.sched_priority = pmin + (pmax-pmin) * (pri + 2) / 4;
    return pthread_setschedparam(self, policy, &sp) == 0;
#endif
}

Есть ли какая-то лучшая возможность/API, которую я пропустил?

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
0
68
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Итак, если я хочу быть ориентированным на будущее, но при этом иметь возможность регулировать приоритет потоков с помощью политики планирования по умолчанию SCHED_OTHER, что мне следует делать?

Это хороший вопрос.

К сожалению, если Linux когда-либо будет обновлен, чтобы сделать качество атрибутом каждого процесса, а не атрибутом потока, то мы не знаем, какие меры будут приняты, чтобы компенсировать влияние на приоритезацию потоков внутри процесса. Мы также не знаем, смогут ли glibc или другие стандартные библиотеки C адаптировать свои функции-оболочки и каким образом. Например, возможно, что оболочка setpritority() Glibc будет каким-то образом изменена, чтобы продолжать обеспечивать расстановку приоритетов для каждого потока. Или нет. Также неясно, будет лиSCHED_OTHER приобретать более широкий диапазон приоритетов планирования. Неясно, может ли быть введена новая «нормальная» политика планирования.

На ум приходит возможность работать в резервном режиме: запустите sched_get_priority_min/sched_get_priority_max dance, и если окажется, что это [0,0] И мы находимся в Linux, предположим, что мы находимся в ситуации, когда значение nice указано для каждого потока. (идея состоит в том, что если значение nice когда-либо создается для каждого потока, материал pthreads должен начать работать должным образом, с диапазоном пригодных значений).

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

Есть ли какая-то лучшая возможность/API, которую я пропустил?

Я так не думаю.

Спасибо, это вполне соответствует тому, что я думал; если быть полностью честным, я чувствую, что это в основном упражнение по стилю, и что качество каждого потока никогда не будет изменено, поскольку я ожидаю, что было написано много кода, основанного на текущем поведении. Возможно, pthread_setschedparam/pthread_setschedprio/sched_setparam вступит в силу на SCHED_OTHER (и в этом случае мой код должен работать нормально), и, возможно, setpriority получит немного PRIO_PROCESS_REALLY, но поскольку это так с самого начала NTPL и никогда не менялось, я бы не стал Я задерживаю дыхание.

Matteo Italia 16.05.2024 09:18

Как сказал Джон Боллинджер, существует проблема полагаться на будущее и сомневаться в нем. Любой блок кода, который успешно абстрагирует проблему, необходимо будет поддерживать.

Одна из возможностей — отправить код в намеренно «сломанном, не компилируемом» состоянии, чтобы будущие искатели приключений с кодом были вынуждены учитывать ситуацию. Это может быть обусловлено последними известными хорошими (ну, понятными) версиями библиотек/операционных систем, чтобы при компиляции на их уровне или ниже он работал.

Другая возможность заключается в том, что можно написать функцию «инициализации библиотеки», которая проверяет поведение платформы/библиотеки при запуске приложения, а затем запоминает базовое поведение API. Я не думал, как это может выглядеть, но, вероятно, это достижимо.

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

Matteo Italia 16.05.2024 09:06

Другие вопросы по теме