Я пытаюсь реализовать кросс-платформенную оболочку для настройки приоритета текущего потока; в 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, которую я пропустил?
Итак, если я хочу быть ориентированным на будущее, но при этом иметь возможность регулировать приоритет потоков с помощью политики планирования по умолчанию 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, которую я пропустил?
Я так не думаю.
Как сказал Джон Боллинджер, существует проблема полагаться на будущее и сомневаться в нем. Любой блок кода, который успешно абстрагирует проблему, необходимо будет поддерживать.
Одна из возможностей — отправить код в намеренно «сломанном, не компилируемом» состоянии, чтобы будущие искатели приключений с кодом были вынуждены учитывать ситуацию. Это может быть обусловлено последними известными хорошими (ну, понятными) версиями библиотек/операционных систем, чтобы при компиляции на их уровне или ниже он работал.
Другая возможность заключается в том, что можно написать функцию «инициализации библиотеки», которая проверяет поведение платформы/библиотеки при запуске приложения, а затем запоминает базовое поведение API. Я не думал, как это может выглядеть, но, вероятно, это достижимо.
«Одна из возможностей — отправить код в заведомо «сломанном, не компилируемом» состоянии, чтобы будущие искатели приключений с кодом были вынуждены учитывать ситуацию». проблема здесь в том, что на самом деле это не то, что можно протестировать во время компиляции, а тщательное тестирование поведения платформы не совсем тривиально. Это должно быть сделано при инициализации библиотеки, но тьфу, это больно и довольно агрессивно (единственное, что приходит на ум, это создать поток, сделать setpriority
тут же и затем проверить из основного потока, был ли изменен его собственный приоритет).
Спасибо, это вполне соответствует тому, что я думал; если быть полностью честным, я чувствую, что это в основном упражнение по стилю, и что качество каждого потока никогда не будет изменено, поскольку я ожидаю, что было написано много кода, основанного на текущем поведении. Возможно,
pthread_setschedparam
/pthread_setschedprio
/sched_setparam
вступит в силу наSCHED_OTHER
(и в этом случае мой код должен работать нормально), и, возможно,setpriority
получит немногоPRIO_PROCESS_REALLY
, но поскольку это так с самого начала NTPL и никогда не менялось, я бы не стал Я задерживаю дыхание.