Когда есть несколько однопоточных процессов и несколько многопоточных процессов, все из которых интенсивно используют ЦП, как распределяется время между ними (при условии, что все имеют одинаковый приоритет)?
Например, на 48-ядерной машине у меня 48 однопоточных процессов и один процесс с 48 потоками. Все потоки готовы к использованию CPU. Я ожидал, что 48 однопоточных процессов получат 1/2 доступного ЦП, а 48 потоков получат еще 1/2 ЦП, то есть каждый поток (независимо от того, из однопоточного процесса или из многопоточного процесса. ) получит равное количество процессорного времени.
Но похоже, что время сначала делится между процессами, и каждый процесс получает 1/49 ЦП, а затем эта часть делится между потоками в процессе. В результате потоки в многопоточном процессе получают только 1/48 часть времени, отведенного потоку в однопоточном процессе.
Вопросы: 1) Как работает планировщик? 2) Можно ли заставить планировщик выделять одинаковое время каждому потоку, независимо от того, из какого процесса этот поток исходит?
Я проверил ваше наблюдение, и, по крайней мере, на последних ядрах оно неверно. Я написал этот код.
#define _GNU_SOURCE
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <err.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <sys/time.h>
#include <sys/resource.h>
#define TIMEOUT 4
void print_usage(
char *type)
{
struct rusage use;
getrusage(RUSAGE_THREAD, &use);
float total_time = 0;
long total_sw = 0;
total_time += use.ru_utime.tv_sec + ((float)use.ru_utime.tv_usec / 1000000);
total_time += use.ru_stime.tv_sec + ((float)use.ru_stime.tv_usec / 1000000);
total_sw = use.ru_nvcsw + use.ru_nivcsw;
printf("Type: %s, CPU Time: %.3f seconds, Total context switches: %d\n",
type, total_time, total_sw);
return;
}
struct worksync {
pthread_spinlock_t spin;
};
void * spinner_thread(
void *data)
{
struct worksync *sync = (struct worksync *)data;
pthread_spin_lock(&sync->spin);
print_usage("Thread");
pthread_spin_unlock(&sync->spin);
pthread_exit(0);
}
void spawn_threaded_worker(
int ncpu,
int timeout)
{
pid_t pid;
pid = fork();
if (pid < 0)
err(EXIT_FAILURE, "fork failed");
if (pid == 0) {
/* allocate and initialize structures */
pthread_t *threads = alloca(sizeof(pthread_t) * ncpu);
struct worksync sync;
int i;
pthread_spin_init(&sync.spin, PTHREAD_PROCESS_PRIVATE);
assert(threads);
for (i=0; i < ncpu; i++) {
pthread_create(&threads[i], NULL, spinner_thread, (void *)&sync);
}
pthread_spin_lock(&sync.spin);
sleep(timeout);
pthread_spin_unlock(&sync.spin);
for (i=0; i < ncpu; i++)
pthread_join(threads[i], NULL);
exit(0);
}
}
void spinner_process(
struct worksync *sync)
{
pthread_spin_lock(&sync->spin);
print_usage("Process");
pthread_spin_unlock(&sync->spin);
exit(0);
}
void spawn_forked_worker(
int ncpu,
int timeout)
{
int i;
int status;
pid_t pid;
pid = fork();
if (pid < 0)
err(EXIT_FAILURE, "fork failed");
if (pid == 0) {
pid_t *pids = alloca(sizeof(pid_t) * ncpu);
struct worksync *sync = mmap(NULL, sizeof(struct worksync),
PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_SHARED, -1, 0);
assert(sync != MAP_FAILED);
pthread_spin_init(&sync->spin, PTHREAD_PROCESS_SHARED);
pthread_spin_lock(&sync->spin);
for (i=0; i < ncpu; i++) {
pids[i] = fork();
if (pids[i] < 0)
abort();
if (pids[i] == 0)
spinner_process(sync);
}
sleep(timeout);
pthread_spin_unlock(&sync->spin);
for (i=0; i < ncpu; i++)
wait(&status);
exit(0);
}
}
int main(
void)
{
int ncpu;
int status;
ncpu = sysconf(_SC_NPROCESSORS_ONLN);
assert(ncpu > 0);
printf("Running %d threads and %d processes for %d seconds\n", ncpu, ncpu, TIMEOUT);
spawn_threaded_worker(ncpu, TIMEOUT);
spawn_forked_worker(ncpu, TIMEOUT);
wait(&status);
wait(&status);
exit(0);
}
Он измеряет время ЦП, затрачиваемое на выполнение части работы с интенсивным использованием ЦП (вращение в спин-блокировке) как в многопоточной, так и в разветвленной модели, при одновременном использовании всех ЦП системы. Затем сообщает статистику ЦП.
Мои результаты отображаются на коробке с 4 процессорами:
С ОТКЛЮЧЕННОЙ автогруппой
$ ./schedtest
Running 4 threads and 4 processes for 4 seconds
Type: Thread, CPU Time: 1.754 seconds, Total context switches: 213
Type: Thread, CPU Time: 1.758 seconds, Total context switches: 208
Type: Thread, CPU Time: 1.755 seconds, Total context switches: 217
Type: Process, CPU Time: 1.768 seconds, Total context switches: 251
Type: Process, CPU Time: 1.759 seconds, Total context switches: 209
Type: Thread, CPU Time: 1.772 seconds, Total context switches: 258
Type: Process, CPU Time: 1.752 seconds, Total context switches: 215
Type: Process, CPU Time: 1.756 seconds, Total context switches: 225
При включенной автогруппе
$ ./schedtest
Running 4 threads and 4 processes for 4 seconds
Type: Thread, CPU Time: 0.495 seconds, Total context switches: 167
Type: Thread, CPU Time: 0.496 seconds, Total context switches: 167
Type: Thread, CPU Time: 0.430 seconds, Total context switches: 145
Type: Process, CPU Time: 0.430 seconds, Total context switches: 148
Type: Process, CPU Time: 0.440 seconds, Total context switches: 149
Type: Process, CPU Time: 0.440 seconds, Total context switches: 150
Type: Thread, CPU Time: 0.457 seconds, Total context switches: 153
Type: Process, CPU Time: 0.430 seconds, Total context switches: 144
Вы можете ясно видеть, что ядро не различает потоки и процессы.
Понятия не имею, что вы делаете, но что бы это ни было, это не соответствует тому, как работает Linux, по крайней мере, для меня.
Я думаю, что то, что вы видите, является следствием функция "автогруппировка" из CFS планировщик, который пытается сгруппировать процессы (и потоки), которые используют один и тот же «сеанс» (как в сеансах, запущенных вызовом setsid()
.)
(Я предполагаю, что вы запускаете 48 однопоточных процессов каждый в отдельном сеансе.)
Вы можете попытаться отключить функцию "автогруппировки" с помощью этой команды, чтобы увидеть, изменит ли она поведение, которое вы видите:
echo 0 >/proc/sys/kernel/sched_autogroup_enabled
Увидеть раздел об автогруппе на странице руководства для sched (7) Больше подробностей.