Как хорошие приоритеты и политики планировщика связаны с идентификаторами процессов (потоков?) В Linux?

Я изучаю, как сделать так, чтобы мой рабочий стол Linux оставался плавным и интерактивным, пока я выполняю задачи с интенсивным использованием ЦП в фоновом режиме. Вот пример программы (написанной на Java), которую я использую для имитации загрузки процессора:

public class Spinner {
    public static void main(String[] args)
    {
        for (int i = 0; i < 100; i++) {
            (new Thread(new Runnable() {
                    public void run() {
                        while (true);
                    }
            })).start();
        }
    }
}

Когда я запускаю это в командной строке, я замечаю, что интерактивность моих настольных приложений (например, текстового редактора) значительно падает. У меня двухъядерный компьютер, поэтому меня это не удивляет.

Чтобы бороться с этим, моей первой мыслью было красиво процесс с renice -p 20 <pid>. Однако я обнаружил, что это не сильно влияет. Вместо этого мне нужно перерисовать все дочерние процессы чем-то вроде ls /proc/<pid>/task | xargs renice 20 -p --, что дает гораздо больший эффект.

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

Есть ли у кого-нибудь четкое представление о том, что здесь происходит? Похоже, что каждый поток на самом деле является отдельным процессом (по крайней мере, у него есть действующий PID). Я знал, что исторически Linux работал так, но я полагал, что NPTL исправил это много лет назад.

Я тестирую RHEL 5.4 (ядро Linux 2.6.18).

(Кстати. Я замечаю тот же эффект, если пытаюсь использовать sched_setscheduler(<pid>, SCHED_BATCH, ..), чтобы попытаться решить эту проблему интерактивности. То есть мне нужно сделать этот вызов для всех "дочерних" процессов, которые я вижу в /proc/<pid>/task, этого недостаточно для выполнения это один раз в pid основной программы.)


person pauldoo    schedule 27.07.2011    source источник


Ответы (2)


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

Системные вызовы планировщика обычно относятся к последнему классу, потому что это позволяет вам давать разным потокам внутри процесса разные атрибуты планировщика, что часто бывает полезно.

person caf    schedule 28.07.2011
comment
Ваше объяснение согласуется с тем, что я наблюдаю. Жаль, что страницы руководства для nice и sched_setparam () относятся к PID, а не к TID. Это чертовски запутанно. - person pauldoo; 28.07.2011
comment
@pauldoo: Конечно. Вы можете сообщать об ошибках на страницах руководства Linux. - person caf; 28.07.2011
comment
В качестве последнего комментария я недавно обнаружил gettid [kernel.org/doc/man-pages/online/pages/man2/gettid.2.html], который получает TID текущего потока. В основном потоке приложения это должно вернуть то же значение, что и знакомый getpid. - person pauldoo; 24.08.2011

Насколько я понимаю, в Linux потоки и процессы в значительной степени одно и то же; потоки просто являются процессами, которые используют одну и ту же память, а не выполняют функцию копирования при записи вилки, и fork (2) и pthread_create (3) предположительно просто наложены на вызов clone (2) с разными аргументами.

Составление расписания очень сбивает с толку, потому что, например, справочная страница pthreads (7) начинается с сообщения ваши потоки Posix разделяют общую приятную ценность, но тогда вам нужно перейти к

NPTL по-прежнему имеет несколько несоответствий с POSIX.1: потоки не имеют общего значения nice

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

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

По крайней мере, это то, что я вспомнил. Я просто впервые за много лет посмотрел на код и увидел, что я на самом деле сделал следующее:

// Note that this code relies on Linux NPTL's non-Posix-compliant
// thread-specific nice value (although without a suitable replacement
// per-thread priority mechanism it's just as well it's that way).
// TODO: Should check some error codes,
// but it's probably pretty harmless if it fails.

  const int current_priority=getpriority(PRIO_PROCESS,0);
  setpriority(PRIO_PROCESS,0,std::min(19u,current_priority+n)); 

Что интересно. Я, вероятно, попробовал nice (2) и обнаружил, что это действительно применимо ко всему процессу (всем потокам), что было не тем, что я хотел (но, возможно, вы это делаете). Но это возвращается на годы назад; поведение могло измениться с тех пор.

Один важный инструмент, когда вы играете с подобными вещами: если вы нажмете 'H' (NB, а не 'h') в top (1), он переходит от представления процесса к отображению всех потоков и значений nice каждого отдельного потока. например, если я запускаю [evolvotron][7] -t 4 -n 5 (4 вычислительных потока при хорошем 5), я вижу (я просто на старой одноядерной машине без HT, поэтому на самом деле здесь не так много смысла в нескольких потоках):

Tasks: 249 total,   5 running, 244 sleeping,   0 stopped,   0 zombie
Cpu(s): 17.5%us,  6.3%sy, 76.2%ni,  0.0%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Mem:   1025264k total,   984316k used,    40948k free,    96136k buffers
Swap:  1646620k total,        0k used,  1646620k free,   388596k cached

  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND           
 4911 local     25   5 81096  23m  15m R 19.7  2.4   0:04.03 evolvotron         
 4912 local     25   5 81096  23m  15m R 19.7  2.4   0:04.20 evolvotron         
 4913 local     25   5 81096  23m  15m R 19.7  2.4   0:04.08 evolvotron         
 4914 local     25   5 81096  23m  15m R 19.7  2.4   0:04.19 evolvotron         
 4910 local     20   0 81096  23m  15m S  9.8  2.4   0:05.83 evolvotron         
 ...
person timday    schedule 28.07.2011
comment
Потоки - это больше, чем просто процессы, которые используют одну и ту же mm и файловую таблицу. Внутренне они являются членами одной и той же группы задач, что имеет другие эффекты - например, сигналы, управляемые процессом, могут обрабатываться любым потоком в группе задач; если какой-либо поток вызывает системный вызов exit_group() (сопоставленный с оболочкой exit() в glibc), вся группа задач завершается; сигнал завершения процесса, такой как SIGSEGV, завершит работу всей группы задач; только лидер группы потоков получает каталог верхнего уровня в /proc. - person caf; 29.07.2011
comment
Круто, спасибо за "топовый" трюк. Похоже, это подтверждает наблюдаемое мной поведение. Жаль, что я не вижу способа показать политику планировщика для каждого потока. - person pauldoo; 29.07.2011