1

I am very new to multi-thread programming and need some pointers on this problem I am having.

I have this object which is expensive to calculate, so what I want to do is calculate it only if needed and once calculated share it with read access if another thread needs it. I only know for which inputs I need the calculations during run time.

So my basic idea was something like this

class ExpansiveDoubleCalc
{
    private:
        static double *ptr[10];
        double DoExpansiveCalc(int input)
        {
            double expansivecalc = (123.4 * input);
            return expansivecalc;
        }

    public:
        ExpansiveDoubleCalc(int input, double* output)
        {
            if(ptr[input]==NULL)
                *ptr[input] = DoExpansiveCalc(input);
            output = ptr[input];
        }

};

double * ExpansiveDoubleCalc::ptr[10]{};

Lets assume that I only need it for input <10. From my little understanding of multi-threading this has numerous problems: * the threads could try to run DoExpansiveCalc at the same time * once they get a calculated pointer to output back, it can be slow if multiple threads try to access it

Is that correct? How do I make it safe? Also, I should probably return a const pointer here right? is there a nice way to do this?

Thanks for the help!! Cheers!

the.polo
  • 347
  • 3
  • 11
  • 1
    Is `ExpansiveDoubleCalc()` purely functional? or does it rely on some program state? (btw, the key word you are looking for is **multithreaded memoization**) –  Dec 07 '18 at 14:24
  • The answer to your question will be found in your C++ book's chapter that explains how to use `std::mutex`es. – Sam Varshavchik Dec 07 '18 at 14:24
  • Hi! Thanks @Frank having the right terminalogy is very helpful when searching this kind of stuff. – the.polo Dec 07 '18 at 14:30
  • @SamVarshavchik do you have a nice C++ book reference for me? – the.polo Dec 07 '18 at 14:30
  • @Frank actually it will be purely functional! Does that help me? I thought keeping my results in a static array in a class would be nice. – the.polo Dec 07 '18 at 14:34
  • https://stackoverflow.com/questions/388242/the-definitive-c-book-guide-and-list – Sam Varshavchik Dec 07 '18 at 14:34
  • 2
    This answer might help: https://stackoverflow.com/a/36240326/580083. The solution is based on two atomic "flags" (bool variables). Once the calculation is performed, only a single read of one atomic variable is done while accessing calculated data. – Daniel Langr Dec 07 '18 at 15:01
  • 1
    The typical solution would be to either use a mutex (look up `std::mutex` and `std::lock_guard`, or an array of atomic variables (look up `std::atomic`). If your expensive calculation is purely functional, you could perform it in more than one thread simultaneously, but I don't see how that would help you. You would still need mutexes or atomic variables to store the results. – PlinyTheElder Dec 07 '18 at 15:09

2 Answers2

1

Regular lock based solution in modern cpp: https://gcc.godbolt.org/z/SsQaEB

Some notes:

  1. I am setting the size at run time. change to std::array if you know the size at compile time.
  2. Usually not recommended to use static global state. Use a rawptr or std::shared_ptr to share the object explicitly.

#include<mutex>
#include<vector>

class ExpensiveDoubleCalc {
    public:
    ExpensiveDoubleCalc(size_t size) : data(size){
    }

    static double DoExpensiveCalc(int input) {
        return 123.4 * input;
    }

    double get(int input) {
        return data.at(input).get(input);
    }

    private:
    struct Data {
        bool isSet{false};
        double val;
        std::mutex m;
        double get(int input){
            std::lock_guard<std::mutex> lock{m};
            if(isSet){
                return val;
            } else {
                val = DoExpensiveCalc(input);
                isSet = true;
                return val;
            }
        }
    };    
    std::vector<Data> data;

};
balki
  • 26,394
  • 30
  • 105
  • 151
0

So in the end I went with the Meyers Singleton which holds an array that stores the expensive calculation for each int input, calculating it only if the array entry is not there. If someone is interested I can put together a code example.

the.polo
  • 347
  • 3
  • 11