我有以下类允许通过昂贵的计算设置值,我们可以假装计算在其他一些线程中异步发生。该类还有一个获取当前值的方法,如果需要可能同步进行该计算:
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);
};
让我们假装这个功能:
void SomeOtherFunc(int x, const std::function<void(int)>& func,
bool blocking) {
// Do something expensive, in another thread, possibly blocking.
func(x * 2);
}
这是我想要的方式:
Example e1, e2;
e1.Set(5);
// "10 2"
std::cout << e1.Get() << " " << e2.Get();
我关注的是以下情况:
Example e;
e.Set(10); // Takes a while
e.Get(); // Need the value now
我的问题:
Example
类是否是线程安全的?如果没有,怎么会这样呢?似乎锁可以工作,但可能有点矫枉过正。SomeOtherFunc
中的工作很昂贵,我只想调用一次该函数。在调用之前将类中的标志设置为true,并在调用之前检查,这似乎是合理的(但它是否是线程安全的?)。问题是,如果我之后立即致电Set
然后Get
,我希望Get
返回&#34;最终&#34; x_
的值。我该如何确保?也就是说,如果设置了该标志,如何让Get
等待回调以线程安全的方式完成?答案 0 :(得分:1)
您可能想要使用future。
类模板std :: future提供了一种访问异步操作结果的机制: 异步操作(通过std :: async,std :: packaged_task或std :: promise创建)可以为该异步操作的创建者提供std :: future对象。 然后,异步操作的创建者可以使用各种方法来查询,等待或从std :: future中提取值。如果异步操作尚未提供值,则这些方法可能会阻塞。 当异步操作准备好将结果发送给创建者时,它可以通过修改链接到创建者的std :: future的共享状态(例如std :: promise :: set_value)来实现。
答案 1 :(得分:1)
您的Example
类不是线程安全的,因为可以同时修改整数x_
,这可能导致未定义的行为(数据竞争)。
此外,您可以多次执行昂贵的计算,因为您在Get()
中遇到竞争条件,方法是检查x_
然后调用将设置它的函数。
您需要一种机制来保证x_
的值只计算一次,同时完全线程安全(Set()
和Get()
可以同时调用)。
标准库提供了一种精确解决该问题的机制call_once()
,旨在实现一次性事件,同时在线程之间正确同步共享数据。
您可以像这样使用它:
#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;
...
};
这也处理了Get()
在回调处理结果时必须等待的情况。
请注意,您不再(并且必须)检查x_
中Get()
的值,因为这将构成数据竞争(另一个线程可能正在更新x_
同时)。
另请注意,直接调用回调Finished()
并非线程安全。可能最好将其移至private:
部分。