Проблема с политикой планирования потоков, временем переключения контекста и общим временем ЦП для потока. Linux - PullRequest
2 голосов
/ 23 марта 2020

Во-первых, у меня проблема с Java 11 (OpenJdk, Debian).

Все, что мне нужно, это просто прервать поток после 20-миллисекундного срока. Например, как в клиентской библиотеке Google GRP C java. Таким образом, обычный способ добиться этого - создать ScheduledThreadPoolExecutor и отправить задачу Watchdog с задержкой (в моем случае 20 мс). После этого нам просто нужно отправить прерывание в рабочий поток, который ожидает. Конечно, потоки сторожевого таймера должны иметь максимальный приоритет.

Поток сторожевого таймера срабатывает почти точно через 20 мс в случае низкой загрузки системы. И это ожидается. Но в случае 100% загрузки системы другими потоками (количество потоков совпадает с номером ядра ЦП или в два раза больше) ScheduledThreadPoolExecutor.schedule с задержкой 20 мс может просыпаться иногда через 25 мс или 40 и более и более миллисекунд!

Как мой единственный поток с наивысшим приоритетом не может получить процессорное время более 40 мс?

Я пробовал это на своем ноутбуке с ядром i7 6 с ядром HT (12 логических) / nix 5.3.0- 40-generi c # 32 ~ 18.04.1-Ubuntu SMP

После этого я написал код на C ++ для проверки точности сна и пробуждения потока с высоким приоритетом, и это происходит снова! Поток сторожевого таймера просто спит в течение 1 мс (требуется проверить состояние 1000 раз в секунду). Поведение несколько сумасшедшее.

Более того - на ноутбуке моего друга все работает нормально с более чем 100 рабочими потоками, показывающими всегда задержку менее 10 с нами. Программа работает под sudo, чтобы установить приоритет потока.


g ++ -pthread -o темы потоков. cpp

#include <iostream>
#include <thread>
#include <vector>
#include <algorithm>
#include <chrono>
#include <cstring>
#include <pthread.h>

void watchdog() {

    long i = 9000000000;
    auto last = std::chrono::high_resolution_clock::now();
    int sleep_duration_us = 1000;

    while (i-- > 0) {
        std::this_thread::sleep_for (std::chrono::microseconds(sleep_duration_us));
        auto now = std::chrono::high_resolution_clock::now();

        auto delay = std::chrono::duration_cast<std::chrono::microseconds>(now-last).count() - sleep_duration_us;

        if (i % 100 == 0)
            std::cout << "Awakening delay: " << delay << " us\n";

        if (delay > 1000)
            std::cerr << "LONG DELAY!!! " << delay << " us" << std::endl;

        // if (sleep_time > 1000000) {
        //     std::cout << "\n watchdog fail - " << sleep_time ;
        // }
        // if (i % 10000000 == 0)
        //     std::cout << "\n watchdog - " << std::chrono::duration_cast<std::chrono::nanoseconds>(now-last).count() ;
        last = now;
    }
}

int main()
{
    // vector container stores threads
    std::vector<std::thread> workers;
    for (int i = 0; i < 10; i++) {
        auto t = std::thread([]() 
        {
            int i = 0, j = i;
            while (true) {

                if (j % 2 == 0)
                    j /= 2;
                else
                    j = 3 * j + 1;

                if (j == 123456789)
                    std::cout << j << std::endl;
            }
        });

        workers.push_back(std::move(t));
    }

    std::cout << "main thread\n";

    std::thread tw(watchdog);

    sched_param sch;        
    int policy;
    pthread_getschedparam(tw.native_handle(), &policy, &sch);
    std::cout << "default priority " << sch.sched_priority << std::endl;

    sch.sched_priority = 99;
    if(pthread_setschedparam(tw.native_handle(), SCHED_RR, &sch)) {
        std::cout << "Failed to setschedparam: " << std::strerror(errno) << '\n';
    }

    pthread_getschedparam(tw.native_handle(), &policy, &sch);
    std::cout << "new priority " << sch.sched_priority << std::endl;

    std::for_each(workers.begin(), workers.end(), [](std::thread &t)
    {
        std::cout << t.get_id() << "\n";
    });

    std::this_thread::sleep_for(std::chrono::seconds(15));
    std::cout << "finish\n";

    return 0;
}

Мой вывод:

root@openminder-nix:/source/scheduler_test# sudo ./threads 
main thread
default priority 0
new priority 99
140018767865600
140018759472896
140018751080192
140018742687488
140018734294784
140018725902080
140018717509376
140018709116672
140018700723968
140018692331264
Awakening delay: 2 us
Awakening delay: 2 us
Awakening delay: 2 us
Awakening delay: 2 us
Awakening delay: 2 us
Awakening delay: 3 us
Awakening delay: 4 us
Awakening delay: 2 us
Awakening delay: 2 us
Awakening delay: 2 us
Awakening delay: 2 us
Awakening delay: 3 us
Awakening delay: 2 us
Awakening delay: 3 us
Awakening delay: 2 us
LONG DELAY!!! 2048 us
Awakening delay: 7 us
Awakening delay: 4 us
Awakening delay: 5 us
LONG DELAY!!! 2199 us
Awakening delay: 5 us
Awakening delay: 4 us
Awakening delay: 6 us
Awakening delay: 5 us
Awakening delay: 8 us
Awakening delay: 5 us
Awakening delay: 5 us
Awakening delay: 4 us
Awakening delay: 6 us
LONG DELAY!!! 1195 us

Поэтому, пожалуйста, помогите мне понять механизм планирования потоков linux и написать код с точностью сторожевого таймера в 1 мс.

...