Tengo dos tareas computacionales diferentes que tienen que ejecutarse a ciertas frecuencias. Uno debe realizarse cada 1 ms y el otro cada 13,3 ms. Las tareas comparten algunos datos.

Estoy teniendo dificultades para programar estas tareas y cómo compartir datos entre ellas. Una forma en que pensé que podría funcionar es crear dos hilos, uno para cada tarea.

La primera tarea es relativamente más simple y se puede manejar en 1ms. Pero, cuando se inicie la segunda tarea (que es relativamente más lenta), hará una copia de los datos que acaba de utilizar la tarea 1 y continuará trabajando en ellos.

¿Crees que esto funcionaría? ¿Cómo se puede hacer en c ++?

1
mfaieghi 10 sep. 2018 a las 17:29

3 respuestas

La mejor respuesta

Hay varias formas de hacerlo en C ++.

Una forma simple es tener 2 hilos, como lo describiste. Cada hilo realiza su acción y luego duerme hasta que comience el siguiente período. Un ejemplo de trabajo:

#include <functional>
#include <iostream>
#include <chrono>
#include <thread>
#include <atomic>
#include <mutex>

std::mutex mutex;
std::atomic<bool> stop = {false};
unsigned last_result = 0; // Whatever thread_1ms produces.

void thread_1ms_action() {
    // Do the work.

    // Update the last result.
    {
        std::unique_lock<std::mutex> lock(mutex);
        ++last_result;
    }
}

void thread_1333us_action() {
    // Copy thread_1ms result.
    unsigned last_result_copy;
    {
        std::unique_lock<std::mutex> lock(mutex);
        last_result_copy = last_result;
    }

    // Do the work.
    std::cout << last_result_copy << '\n';
}

void periodic_action_thread(std::chrono::microseconds period, std::function<void()> const& action) {
    auto const start = std::chrono::steady_clock::now();
    while(!stop.load(std::memory_order_relaxed)) {
        // Do the work.
        action();

        // Wait till the next period start.
        auto now = std::chrono::steady_clock::now();
        auto iterations = (now - start) / period;
        auto next_start = start + (iterations + 1) * period;
        std::this_thread::sleep_until(next_start);
    }
}

int main() {
    std::thread a(periodic_action_thread, std::chrono::milliseconds(1), thread_1ms_action);
    std::thread b(periodic_action_thread, std::chrono::microseconds(13333), thread_1333us_action);

    std::this_thread::sleep_for(std::chrono::seconds(1));
    stop = true;
    a.join();
    b.join();
}

Si ejecutar una acción demora más de uno period en ejecutarse, duerme hasta que comience el siguiente período (omite uno o más períodos). Es decir. cada enésima acción ocurre exactamente en start_time + N * period, por lo que no hay deriva de tiempo, independientemente del tiempo que lleve realizar la acción.

Todo el acceso a los datos compartidos está protegido por el mutex.

3
Maxim Egorushkin 11 sep. 2018 a las 09:24

Así que estoy pensando que task1 necesita hacer la copia, porque sabe cuándo es seguro hacerlo. Aquí hay un modelo simplista:

Shared:
    atomic<Result*> latestResult = {0};

Task1:
    Perform calculation
    Result* pNewResult = new ResultBuffer
    Copy result to pNewResult
    latestResult.swap(pNewResult)
    if (pNewResult)
        delete pNewResult; // Task2 didn't take it!

Task2:
    Result* pNewResult;
    latestResult.swap(pNewResult);
    process result
    delete pNewResult;

En este modelo, la tarea 1 y la tarea 2 solo se quejan cuando se intercambia un puntero atómico simple, lo cual es bastante indoloro.

Tenga en cuenta que esto hace muchas suposiciones sobre su cálculo. ¿Podría su tarea1 calcular útilmente el resultado directamente en el búfer, por ejemplo? También tenga en cuenta que al inicio, Task2 puede encontrar que el puntero sigue siendo nulo.

También ineficientemente nuevo () s las memorias intermedias. Necesita 3 memorias intermedias para asegurarse de que nunca haya una discusión significativa entre las tareas, pero puede administrar tres punteros de memoria intermedia bajo mutexes, de modo que la Tarea 1 tenga un conjunto de datos listo y escriba otro conjunto de datos, mientras que la tarea 2 está leyendo de un tercer set.

Tenga en cuenta que incluso si tiene la tarea 2 copiar el búfer, la Tarea 1 todavía necesita 2 búferes para evitar paradas.

1
Gem Taylor 10 sep. 2018 a las 17:52

Puede usar subprocesos de C ++ e instalaciones de subprocesos como la clase thread y clases de temporizador como steady_clock como se ha descrito en la respuesta anterior, pero si esta solución funciona fuertemente depende de la plataforma en la que se esté ejecutando su código.

1 ms y 13.3 ms son intervalos de tiempo bastante cortos y si su código se ejecuta en un sistema operativo en tiempo no real como Windows o Linux no RTOS, no hay garantía de que el programador del sistema operativo active sus hilos en momentos exactos.

C ++ 11 tiene la clase high_resolution_clock que debería usar un temporizador de alta resolución si su plataforma admite uno, pero aún depende de la implementación de esta clase. Y el problema más grande que el temporizador es usar las funciones de espera de C ++. Ni C ++ sleep_until ni sleep_for garantizan que despertarán su hilo en momentos específicos. Aquí está la cita de la documentación de C ++.

sleep_for: bloquea la ejecución del subproceso actual para al menos la sleep_duration especificada. sleep_for

Afortunadamente, la mayoría de los sistemas operativos tienen algunas instalaciones especiales como los temporizadores multimedia de Windows que puede usar si sus hilos no se despiertan en los momentos esperados.

Aquí hay más detalles. Se necesita un sueño de hilo preciso. Error máximo de 1 ms

1
Timmy_A 11 sep. 2018 a las 08:32