1

In the situation where you have a high priority real-time data processing thread reading from a parameter and a second thread with lower priority writing to the parameter (or vice versa) you can end up with a data race.

One solution would be to let the parameter be an std::atomic or to protect the reading/writing of the parameter with mutexes. According to this talk it's best to avoid non-lock free atomics and mutexes to solve such a data race for the following reasons:

  1. The real-time thread will occasionally have to wait to acquire the lock. The execution time of the real-time code, which has to meet a deadline, will suddenly have to take into account any code that can be executed inside the lock protected section of the low priority thread.
  2. If the code inside the lock protected section in the low priority thread contains e.g. memory allocations/deallocation, system calls, or other calls with unbounded execution time the real-time thread can no longer guarantee that it's able meet it's deadline.
  3. The low priority thread can be interrupted by threads which have a higher priority but a priority still below that of the real-time thread, leading to priority inversion.

If one can disregard the last point due to an OS with priority inheritance, is it still advisable to avoid locking even if all lock protected sections are contained inside the same class such as in this example:

#include <mutex>

class RealTimeProcessor
{

     public:
     
        struct Parameter
        {
           // some non atomic data
        };

        void processData()
        {
           Parameter p;

           mtx.lock();
           p = m_param;
           mtx.unlock();

           // do some processing with the local copy of the parameter...
        }

        void setParameter(Parameter param)
        {
           mtx.lock();
           m_param = param;
           mtx.unlock();
        }

     private:

        Parameter m_param;
        std::mutex mtx;
};

Where processData() is called from the real-time thread and setParameter() is called from the low priority thread.

My thought is that since all locking is done within the class, I can make sure that no calls with unbounded execution time are made within the lock protected areas, and that the total worst case completion time of the real-time processing still meets the deadline even in the worst case when having to wait to acquire the lock.

sbh
  • 23
  • 3
  • 2
    Just mutex it, or if you can, avoid sharing data in the first place. It doesn't matter if this is contained within a single class, what matters is how many readers and writers (threads) you have. If there's multiple writers you need mutexes. – tadman Jan 04 '23 at 19:56
  • You should probably have a lock around `Parameter p = m_param` to avoid initializing, and then immediately stomping and rewriting it. – tadman Jan 04 '23 at 19:58
  • 1
    Is he realtime thread supposed to process the same data over and over again until the non-realtime thread changes it? Will it come to different results if it processes the same thing a second time? Something looks odd about this setup – Ted Lyngmo Jan 04 '23 at 20:08
  • 1
    Yes, it is still advisable to avoid need for synchronisation where possible. No matter how well you believe you can "make sure" you avoid unbounded execution time, the fact of synchronisation makes things unpredictable - the issue is not so much time your code executes while in the "lock protected area" as it is acquiring the lock when multiple threads are (potentially or actually) competing to acquire it. Essentially that makes your code susceptible to whatever strategy the OS uses to pick which thread gains access. – Peter Jan 04 '23 at 20:10
  • Side note: If you gotta use a mutex, guard the sucker with your life and do everything in your power to not have to manually lock and unlock it because it introduces too many possibilities for failure. Use a [RAII wrapper class](https://stackoverflow.com/questions/2321511/what-is-meant-by-resource-acquisition-is-initialization-raii) like [`std::scoped_lock`](https://en.cppreference.com/w/cpp/thread/scoped_lock). Example: https://godbolt.org/z/zjzaP81Pq – user4581301 Jan 04 '23 at 20:11
  • Another `scoped_lock` version: `Parameter p = [this]{ std::scoped_lock lock(mtx); return m_param; }();` – Ted Lyngmo Jan 04 '23 at 20:12

0 Answers0