c++multithreading

Multi-threaded input processing


I am working on a program that handles mouse movement, it consists of two threads, the main thread gets the input and stores the mouse position in a fixed location and the child thread loops through that location to get the value:

void Engine::InputManager::MouseMove(const MouseMoveEvent& ev)
{
    cur_mouse_ev_.x_ = ev.x_;
    cur_mouse_ev_.y_ = ev.y_;
    cv_.notify_all();
}

void Engine::InputManager::ProcessInput(MouseMoveEvent* ev)
{
    while (true)
    {
        cv_.wait(u_mutex_);
        float dx = static_cast<float>(ev->x_ - pre_mouse_pos[0]) * 0.25f;
        float dy = static_cast<float>(ev->y_ - pre_mouse_pos[1]) * 0.25f;
        g_pGraphicsManager->CameraRotateYaw(dx);
        pre_mouse_pos[0] = ev->x_;
        pre_mouse_pos[1] = ev->y_;
    }
}

How do I reduce CPU utilization, I am using conditional variables to achieve this, is there a better way to do this? It seems that adding a delay to the subthreads would also work


Solution

  • Using std::condition_variable is a good and efficient way to achieve what you want.

    However - you implementation has the following issue: std::condition_variable suffers from spurious wakeups. You can read about it here: Spurious wakeup - Wikipedia.

    The correct way to use a condition variable requires:

    1. To add a variable (bool in your case) to hold the "condition" you are waiting for. The variable should be updated under a lock using the mutex.

    2. Again under a lock: calling wait in a loop until the variable satisfies the condition you are waiting for. If a spurious wakeup will occur, the loop will ensure getting into the waiting state again. BTW - wait method has an overload that gets a predicate for the condition, and loops for you.

    You can see some code examples here: Condition variable examples.

    A minimal sample that demonstrates the flow:

    #include <thread>
    #include <mutex>
    #include <condition_variable>
    
    std::mutex              mtx;
    std::condition_variable cond_var;
    bool                    ready{ false };
    
    void handler()
    {
        {
            std::unique_lock<std::mutex> lck(mtx);
            cond_var.wait(lck, []() { return ready; }); // will loop internally to handle spurious wakeups
        }
        // Handle data ...
    }
    
    void main()
    {
        std::thread t(handler);
    
        // Prepare data ...
        std::this_thread::sleep_for(std::chrono::seconds(3));
    
        {
            std::unique_lock<std::mutex> lck(mtx);
            ready = true;
        }
        cond_var.notify_all();
        t.join();
    }
    

    Note:
    Accessing the data (both when creating it and consuming it) must be done with proper synchronization (either holding the mutex or in any other appropriate way).
    The minimum that is required is to hold the mutex while copying the data in the handler and then process the copy.
    Atlertatively the data can be accessed without a copy, but with proper synchronization all the way.
    This is left out of the example above for simplicity.