1

Briefly, I have a class that lazily initializes one of its data members and I'd like to figure out the best way to do this in a multithreaded environment.

In more detail, my class currently looks something like this:

#include <algorithm>
#include <optional>
#include <vector>

class A_single_threaded
{
public:
   bool query(const int val) const
   {
      // if opt_vec is not initialized, do that now
      if (!(opt_vec.has_value()))
         initialize_vec();

      // return true if opt_vec contains val
      return std::find(std::cbegin(opt_vec), std::cend(opt_vec), val) != std::cend(opt_vec);
   }
private:
   mutable std::optional<std::vector<int>> opt_vec;

   // sets opt_vec
   void initialize_vec() const;
};

initialize_vec is the only method that modifies opt_vec, and query is the only method that calls initialize_vec. opt_vec can potentially be empty after initialize_vec returns, so giving the data member std::optional type helps distinguish when it's unset and when it's set and empty. In other instances opt_vec winds up being large and initializing it is time-consuming. And since not every A_single_threaded instance will need to run query anyway, it makes sense to avoid initializing opt_vec until a user call to query makes clear that initialization is necessary.

The approach above seems OK for a single thread, but I think it isn't naturally multithread-able. The calls to opt_vec.has_value() and initialize_vec are necessarily unsynchronized, and the gap between their return times allows for a data race that I don't think can be fixed with a mutex. Instead I think the correct solution involves replacing the std::optional with std::call_once, something like the below:

#include <algorithm>
#include <mutex>
#include <vector>

class A_multi_threaded
{
public:
   bool query(const int val) const
   {
      // if opt_vec is not initialized, do that now
      std::call_once(opt_vec_flag, initialize_vec, this);

      // return true if opt_vec contains val
      return std::find(std::cbegin(opt_vec), std::cend(opt_vec), val) != std::cend(opt_vec);
   }
private:
   mutable std::vector<int> opt_vec;
   mutable std::once_flag opt_vec_flag;

   // sets opt_vec
   void initialize_vec() const;
};

I'd appreciate answers to a couple of questions:

  1. Is the implementation I sketched for A_multi_threaded actually thread-safe?
  2. Will A_multi_threaded have the same behavior as A_single_threaded in a single-threaded environment?
  3. Is there a sensible implementation of A_multi_threaded that mimics the implementation of A_single_threaded?
8
  • FYI: std::call_once I had most doubts whether all threads will see the correct initialized opt_vec. (As long as no thread is writing no sync. is needed but the initialization is the (exceptional) writing. So...) However, I found (in this link) this means that all concurrent calls to call_once are guaranteed to observe any side-effects made by the active call, with no additional synchronization. (Emphasize mine.) IMHO, this describes that my doubts are wrong. Commented Nov 29, 2022 at 6:51
  • 1
    std::call_once(opt_vec_flag, initialize_vec); needs to be std::call_once(opt_vec_flag, initialize_vec, this); and using the flag like this will make the class non-movable. Aside from that I don't see any issue as far as the shown code goes. You also potentially need to make sure that destruction of A_multi_threaded is synchronized, e.g. by using a std::shared_ptr. Commented Nov 29, 2022 at 6:56
  • And opt_vec_flag also needs to be mutable (so I am not sure whether it really makes sense to have the functions be const). Commented Nov 29, 2022 at 6:59
  • 1
    Maybe using a local static initializer ? Commented Nov 29, 2022 at 7:43
  • 1
    @TerryWu That would be a simpler approach if A_multi_threaded is supposed to be a singleton, but doesn't work if there are supposed to be multiple A_multi_threaded instances. Commented Nov 29, 2022 at 8:00

0

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.