mardi 24 novembre 2015

Use condition_variable to clock multiple threads?

I have a program with multiple threads, and I would like to have them all perform an asynchronous I/O action at given clock intervals (mostly to throttle them). I want the clock to be as accurate as I can make it, but assume I'm OK with the obvious constraints of trying to have an accurate clock in software. I don't think I want to put all the I/O objects in the busy-wait thread (and thus achieve inherent synchronization), because imagine there are so many of them that it substantially degrades the clock calculation. Assume each I/O operation individually is capable of keeping up with my clock period.

I'm wondering if I can use a condition_variable in a single busy loop to give me the highest possible precision timing, and then "tick" via a notify_all(). Here's conceptually what I would do:

Class

class clocker
{
public:

    // Methods which spawn the threads, etc...

private:
    std::atomic<std::chrono::high_resolution_clock::rep>    m_clockPeriod_ns;
    std::atomic<size_t>                                     m_elapsedTicks;
    std::condition_variable                                 m_tick;
    std::mutex                                              m_tickMutex;
};

Clock Thread:

while (true)
{
    auto end = std::chrono::high_resolution_clock::now();
    std::chrono::high_resolution_clock::duration elapsed_ns;

    // busy look timer for highest possible precision.
    auto start = end;
    while (elapsed_ns.count() < m_clockPeriod_ns)
    {
        end = std::chrono::high_resolution_clock::now();
        elapsed_ns = end - start;
    }
    // it's possible the elapsed time is long relative to the clock period IF the high res clock
    // doesn't actually have enough resolution. To get around this, figure out how many 'ticks' 
    // should have occurred.
    m_elapsedTicks.store(elapsed_ns.count() / m_clockPeriod_ns);
    m_tick.notify_all();
}

I/O Thread:

while (true)
{
    std::unique_lock<std::mutex> lock(m_tickMutex);
    m_tick.wait(lock);

    for (int i = 0; i < m_elapsedTicks.load(); ++i)
    {
        // Do I/O operation
    }           
}

I see some issues with this approach, but I'm not totally sure how important they really are, or how necessarily to get around them:

  1. The documentation mentions that implementations may have spurious wakeups. This could be a problem, but is it really? Do common implementations (gcc, msvc) have spurious wakeups? In practice, how spurious are they? I think I can live with the occasional spur, but it would be a problem if the spur-to-notify ratio was > 1 certainly.
  2. Are there other glaring issues that I'm not seeing?

Aucun commentaire:

Enregistrer un commentaire