Reputation: 393
I have the following class that allows setting a value via an expensive computation, and we can pretend that computation takes place asynchronously in some other thread. The class also has a method to get the current value, perhaps doing that computation synchronously if needed:
class Example {
public:
Example() : x_(0) {}
void Set(int x) {
SomeOtherFunc(x, callback_, false);
}
void Finished(int y) {
x_ = y;
}
const int Get() {
if (!x_) {
SomeOtherFunc(1, callback_, true);
}
return x_;
}
private:
int x_;
std::function<void(int)> callback_ =
std::bind(&Example::Finished, this, std::placeholders::_1);
};
And let's pretend this function:
void SomeOtherFunc(int x, const std::function<void(int)>& func,
bool blocking) {
// Do something expensive, in another thread, possibly blocking.
func(x * 2);
}
This works the way I would like:
Example e1, e2;
e1.Set(5);
// "10 2"
std::cout << e1.Get() << " " << e2.Get();
My concerns are is the following scenario:
Example e;
e.Set(10); // Takes a while
e.Get(); // Need the value now
My questions:
Example
class thread-safe? If not, how can it be made so? Seems like locks would work, but could be overkill.SomeOtherFunc
is expensive, I only want to call that function once. Having a flag inside the class that gets set to true whenever a call is made, and is checked before a call is made, seems reasonable (but is it thread-safe?). The problem is, if I call Set
and then Get
immediately afterwards, I want Get
to return the "final" value for x_
. How can I ensure that? That is, if that flag is set, how do I make Get
wait for the callback to complete in a thread-safe way?Upvotes: 2
Views: 839
Reputation: 6657
Your Example
class is not thread-safe because the integer x_
can be modified concurrently, which may lead to undefined behavior (data race).
In addition, your expensive calculation may be performed multiple times since you have a race condition in Get()
by checking x_
and then calling the function that will set it.
You need a mechanism that guarantees that the value for x_
will be calculated exactly once while fully thread-safe (Set()
and Get()
may be called concurrently).
The standard library provides a mechanism to exactly solve that problem, call_once()
, designed to implement one-shot events while shared data is properly synchronized between threads.
You can use it like this:
#include <mutex>
class Example {
public:
...
void Set(int x) {
std::call_once(flag, SomeOtherFunc, x, callback_, false);
}
...
const int Get() {
std::call_once(flag, SomeOtherFunc, 1, callback_, true);
return x_;
}
private:
std::once_flag flag;
...
};
This also handles the scenario whereby Get()
has to wait while your callback is working on the result.
Note that you no longer can (and have to) check for the value of x_
in Get()
because that would constitute a data race (another thread might be updating x_
at the same time).
Also note that it is not thread safe to call your callback Finished()
directly. Probably best to move that to the private:
section.
Upvotes: 1
Reputation: 27577
You probably want to use a future.
The class template std::future provides a mechanism to access the result of asynchronous operations: An asynchronous operation (created via std::async, std::packaged_task, or std::promise) can provide a std::future object to the creator of that asynchronous operation. The creator of the asynchronous operation can then use a variety of methods to query, wait for, or extract a value from the std::future. These methods may block if the asynchronous operation has not yet provided a value. When the asynchronous operation is ready to send a result to the creator, it can do so by modifying shared state (e.g. std::promise::set_value) that is linked to the creator's std::future.
Upvotes: 1