Linux SCHED_FIFO non rispetta le priorità dei thread

Sep 01 2020

Scenario

Ho creato tre thread, fissati a un singolo core , con le seguenti priorità sotto SCHED_FIFO:

  1. principale :sched_priority = 99
  2. thread_1 :sched_priority = 97
  3. thread_2 :sched_priority = 98

I thread di lavoro ( thread_1, thread_2) calcolano la somma di 50.000.000 numeri primi (~ 10s). Non bloccano o eseguono chiamate di sistema fino alla fine (per stampare l'output).

Il thread principale rimane inattivo per un secondo, quindi controlla le promesse dei thread di lavoro per vedere se è stato completato.


Comportamento atteso

Il thread principale ha la massima priorità. Secondo sched :

Un thread SCHED_FIFO viene eseguito fino a quando non viene bloccato da una richiesta I / O, viene interrotto da un thread con priorità più alta o chiama sched_yield (2).

Main dovrebbe quindi print ( checking ...), a intervalli di secondi. È la massima priorità, quindi dovrebbe impedire qualsiasi cosa in esecuzione. Quando dorme, si blocca, quindi gli altri thread dovrebbero essere eseguiti.

  • thread_1: Termina per primo, poiché ha la priorità quando la rete principale non è occupata.
  • thread_2: Finisce per ultimo e inizia solo dopo che thread_1è stato completato.

Comportamento effettivo

I fili terminano nell'ordine opposto previsto:

Thread 1 summed 3001134 primes at priority level: 97
Thread 2 summed 3001134 primes at priority level: 98
Main: Checking ...
Main: Task 1 has finished!
Main: Task 2 has finished!
Main: Exiting at priority level: 99

Invertire gli ordini di priorità in modo che principale abbia il minor numero di risultati produce esattamente lo stesso risultato.


Riprodurre

  1. Compilare il programma con g++ -o <exec_name> <file_name>.cpp -pthread
  2. Corri con: sudo taskset --cpu-list 1 ./<exec_name>

Il mio kernel è 5.4.0-42-generic, e la mia distribuzione (se è importante): Ubuntu 18.04.5 LTS. Io non ho il preempt-rtcerotto installato.


Domande simili

  • Ho trovato questa domanda che sembra descrivere gli stessi problemi, ma non è stata data alcuna risposta.

  • Ho anche letto in questa domanda che il mio thread ad alta priorità può essere interrotto, ma non mi interessa fintanto che non può essere interrotto da altri thread nati dallo stesso processo. Non ho abbastanza informazioni sul fatto che questo possa accadere.


Codice di esempio

#include <thread>
#include <mutex>
#include <iostream>
#include <chrono>
#include <cstring>
#include <future>
#include <pthread.h>
#include <math.h>

// IO Access mutex
std::mutex g_mutex_io;

// Computation function (busy work)
static bool isPrime (unsigned int value)
{
    unsigned int i, root;
    if (value == 1)       return false;
    if (value == 2)       return true;
    if ((value % 2) == 0) return false;
    root = (int)(1.0 + sqrt(value));
    for (i = 3; (i < root) && (value % i != 0); i += 2);
    return (i < root ? false : true);
}

// Thread function
void foo (unsigned int id, unsigned int count)
{
    sched_param sch;
    int policy, sum = 0;

    // Get information about thread
    pthread_getschedparam(pthread_self(), &policy, &sch);

    // Compute primes
    for (unsigned int i = 1; i < count; ++i) {
        sum += (isPrime(i) ? 1 : 0);
    }

    // Print
    {
        std::lock_guard<std::mutex> lock(g_mutex_io);
        std::cout << "Thread " << id << " summed " << sum << " primes"
                  << " at priority level: " << sch.sched_priority << std::endl; 
    }

}

int main ()
{
    sched_param sch;
    int policy;

    // Declare and init task objects
    std::packaged_task<void(unsigned int, unsigned int)> task_1(foo);
    std::packaged_task<void(unsigned int, unsigned int)> task_2(foo);

    // Get the futures
    auto task_fut_1 = task_1.get_future();
    auto task_fut_2 = task_2.get_future();

    // Declare and init thread objects
    std::thread thread_1(std::move(task_1), 1, 50000000);
    std::thread thread_2(std::move(task_2), 2, 50000000);

    // Set first thread policy
    pthread_getschedparam(thread_1.native_handle(), &policy, &sch);
    sch.sched_priority = 97;
    if (pthread_setschedparam(thread_1.native_handle(), SCHED_FIFO, &sch)) {
        std::cerr << "pthread_setschedparam: " << std::strerror(errno) 
                  << std::endl;
        return -1;
    }

    // Set second thread policy
    pthread_getschedparam(thread_2.native_handle(), &policy, &sch);
    sch.sched_priority = 98;
    if (pthread_setschedparam(thread_2.native_handle(), SCHED_FIFO, &sch)) {
        std::cerr << "pthread_setschedparam: " << std::strerror(errno) 
                  << std::endl;
        return -1;
    }

    // Set main process thread priority
    pthread_getschedparam(pthread_self(), &policy, &sch);
    sch.sched_priority = 99;
    if (pthread_setschedparam(pthread_self(), SCHED_FIFO, &sch)) {
        std::cerr << "pthread_setschedparam: " << std::strerror(errno)
                  << std::endl;
        return -1;
    }

    // Detach these threads
    thread_1.detach(); thread_2.detach();

    // Check their status with a timeout
    for (int finished = 0; finished < 2; ) {
        std::this_thread::sleep_for(std::chrono::seconds(1));
        {
            std::lock_guard<std::mutex> lock(g_mutex_io);
            std::cout << "Main: Checking ..." << std::endl;
        }
        if (task_fut_1.wait_for(std::chrono::seconds(0)) == std::future_status::ready) {
            {
                std::lock_guard<std::mutex> lock(g_mutex_io);
                std::cout << "Main: Task 1 has finished!" << std::endl;
            }
            finished++;
        }
        if (task_fut_2.wait_for(std::chrono::seconds(0)) == std::future_status::ready) {
            {
                std::lock_guard<std::mutex> lock(g_mutex_io);
                std::cout << "Main: Task 2 has finished!" << std::endl;
            }
            finished++;
        }
    }
    pthread_getschedparam(pthread_self(), &policy, &sch);
    std::cout << "Main: Exiting at priority level: " << sch.sched_priority << std::endl;
    return 0;
}

Esperimenti


L'esecuzione di questo programma con due core sudo taskset --cpu-list 1,2produce il seguente bizzarro output:

Thread 2 computed 3001134 primes at priority level: 98
Thread 1 computed 3001134 primes at priority level: 0
Main: Checking ...
Main: Task 1 has finished!
Main: Task 2 has finished!
Main: Exiting at priority level: 99

La priorità di thread_1è zero.

Se lo espando per includere tre core sudo taskset --cpu-list 1,2,3, ottengo il comportamento che mi aspettavo di voler su single-core:

Main: Checking ...
Main: Checking ...
Main: Checking ...
Main: Checking ...
Main: Checking ...
Main: Checking ...
Main: Checking ...
Main: Checking ...
Main: Checking ...
Main: Checking ...
Main: Checking ...
Main: Checking ...
Main: Checking ...
Main: Checking ...
Main: Checking ...
Main: Checking ...
Main: Checking ...
Thread 2 computed 3001134 primes at priority level: 98
Thread 1 computed 3001134 primes at priority level: 0
Main: Checking ...
Main: Task 1 has finished!
Main: Task 2 has finished!
Main: Exiting at priority level: 99

Riorganizzare l'ordine in cui sono configurate le priorità in modo che il thread principale venga eseguito per primo, non cambia l'output nello scenario originale

Risposte

OlafDietsche Sep 01 2020 at 00:46

Quando inizi i due thread

// Declare and init thread objects
std::thread thread_1(std::move(task_1), 1, 50000000);
std::thread thread_2(std::move(task_2), 2, 50000000);

possono (!) eseguire immediatamente e recuperare i parametri di pianificazione

// Get information about thread
pthread_getschedparam(pthread_self(), &policy, &sch);

anche prima di impostarli con pthread_setschedparam()un altro valore. L'output potrebbe anche mostrare 0 e 0, se entrambi i thread sono pianificati di conseguenza.


I thread secondari possono (!) Essere entrambi pianificati dopo che il thread principale ha impostato la priorità. Quindi otterrai l'output previsto. Ma qualsiasi risultato è possibile.


Quando ti sposti pthread_getschedparam()alla fine del thread appena prima dell'output, è più probabile che tu ottenga l'output previsto di 97 e 98. Ma anche in questo caso entrambi i thread possono essere eseguiti fino alla fine, anche prima che il thread principale sia pianificato per impostare la priorità .