4

I have 2 sets--inputs and outputs--of 70 32-bit integer variables and 70 bools (140 vars altogether). These need to be accessed and modified from 3 threads. What is an appropriate design pattern to facilitate thread-safe read-write access to each of these 140 variables without locking all of them under a single mutex (which I expect will result in bad performance)?

Some details about the performance requirements:

  • Thread 1 ("CAN Serial Communication") receives packets from hardware sensors every 1ms that contain the updated value for one of the 70 input shared variables; the thread updates the variable with that value. Also, every 5ms Thread 1 needs to make a copy of all the 70 output variables.

  • Thread 2 ("Controller") creates a copy of all input variables every 10ms, as well as overwrites all the output variables.

  • Thread 3 ("GUI") makes a copy of all input and output variables every 500ms.

  • The system runs on an ARM Cortex-A8 600Mhz.

One solution is to create a mutex lock for each of the 140 variables, but this feels like a hack. I would then wrap the variables in a class with 140 getters and setters, which also seems ugly.


A side-note about std::atomic:

The other alternative is std::atomic. But I feel it is an advanced and complicated feature, for example I was told on IRC that the following example snippet is not thread-safe, despite looking intuitively like it should be:

typedef struct MyStruct {
        std::atomic<int> a;
        std::atomic<int> b;
}

std::atomic<MyStruct> atomic_struct;
atomic_struct.a = 1;
atomic_struct.b = 2;

// Make a copy of `atomic_struct`
Mystruct normal_struct;
normal_struct = atomic_struct;

// Edit the values of the copied struct and copy the changes back to the `atomic_struct`.
normal_struct.a = 100;
normal_struct.b = 200;
atomic_struct = normal_struct;
8
  • 1
    you really only need 2 pairs of get / set that also take an index - then each thread can lock on the shared instance of that class
    – Caleth
    May 20, 2016 at 9:47
  • @Caleth Hmm I suppose you're right: the getters/setters would take an Enum that defines the 140 variables. Is it still not ugly and bad design to create 140 mutexes? Thanks
    – DBedrenko
    May 20, 2016 at 9:48
  • 1
    I would only use 1 - "a copy of all input and output" implies pain down the road if you lock individually - I would suggest lock on the shared instance, copy it into an unshared instance, unlock for that
    – Caleth
    May 20, 2016 at 9:50
  • @Caleth Thank you for the help. But "1" what? If you mean 1 mutex, will it not affect performance badly, because of how often the locking will happen (with all threads put together, this will be on average more frequent than every 1ms).
    – DBedrenko
    May 20, 2016 at 9:54
  • 1
    One mutex, one shared instance, other nonshared instances of that class. It really depends on how "Immediately up to date" each of the values were. It may be worthwhile splitting into an input object and an output object. Try to minimise the action within each block that aquires the mutex to ameliorate performance - Thread 1 should have the value and index ready and really only be doing a single write, Threads 2 would operate on an unshared local instance of the class, and swap it with the shared one. Thread 3 is just copying. Those are all in the microsecond range for a 600Mhz clock
    – Caleth
    May 20, 2016 at 10:02

2 Answers 2

4

Comments turned into an answer:

You are right to worry about performance with locking everything under one mutex, but the better solution is to make sure there is as little going on as possible inside the lock.

Thread 1 should have the value and index ready and really only be doing a single write. Thread 2 would operate on an unshared local instance of the class, and swap it with the shared one. Thread 3 also has an unshared local instance that it copies the share into each update

2

In your particular case (all variables being scalars, i.e. integral or boolean) you might consider using the atomic facilities of C++11. You need a recent GCC or Clang compiler.

So you would use std::atomic_bool and e.g. std::atomic_int etc... for the types of these variables and use atomic_load & atomic_store. A simple usage would be to systematically use atomic_load & atomic_store for every access (or assignment) to these atomic variables.

For operations dealing on the entire set of the variables (e.g. creating a copy of all of them), you'll need some global mutex.

There is of course a performance penalty, but it is probably much lower than using explicit mutexes (à la std::mutex). I don't know your platform exactly, but I would imagine that each access would be perhaps a few dozen times slower than for ordinary non-atomic data. You need to benchmark. Most mutex implementations (at least on Linux) are using the machine equivalent of atomic scalars coupled to some more complex machinery like futex(7) (and this is why in practice mutexes are slower than atomics).

Alternatively, consider grouping your variables together (in a meaningful way), and have a mutex controlling a small subset (of e.g. half a dozen) of variables.

9
  • Thank you for the suggestion. I have spent an entire day researching std::atomic and my conclusion was that it's too complicated and advanced of a topic, with too many edge cases for it to be worth the risk. I chose to do the safe option which is critical sections with mutexes.
    – DBedrenko
    May 20, 2016 at 10:11
  • 1
    I don't understand why you feel that std::atomic is complicated. What are the edge cases you are referring to? May 20, 2016 at 10:14
  • std::atomic<structOf140Values> shared along with std::atomic_exchange( &shared, local ) and local = std::atomic_load( &shared )
    – Caleth
    May 20, 2016 at 10:18
  • 1
    std::atomic is like std::unique_ptr in that regard - think of it as an "atomic pointer"
    – Caleth
    May 20, 2016 at 11:17
  • 1
    Atomic access fails completely if you try to access two or more variables.
    – gnasher729
    May 20, 2016 at 17:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.