2017-02-10 83 views
2

回调设置类成员,我有下面的类,允许通过昂贵的计算设置一个值,我们可以假装计算异步发生在其他线程。该类还有一个方法,如果需要得到的电流值,也许这样做计算同步:与线程安全的方式

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 

我的问题:

  1. 是在Example类线程安全的?如果不是的话,怎么会这样呢?看起来像锁会起作用,但可能是矫枉过正。
  2. 由于SomeOtherFunc的工作很贵,我只想调用一次这个函数。在类内部设置一个标志,每当调用被创建时被设置为true,并且在调用之前被检查,这似乎是合理的(但它是否是线程安全的?)。问题是,如果我叫Set,然后Get随即,我想Getx_返回“最终”值。我如何确保?也就是说,如果设置了该标志,我该如何让Get等待回调以线程安全的方式完成?

回答

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()有,而你的回调工作的结果等。

请注意,您不再可以(也必须)检查Get()中的x_的值,因为这会构成数据竞争(另一个线程可能同时更新x_)。

另请注意,它不是线程安全的直接调用您的回调Finished()。可能最好将其移至private:部分。

+0

因此,如果一个调用正在进行,call_once会在'Get()中阻塞?这几乎就是我想要的。 –

+0

@SteveD是的,这是'call_once'保证的。对于'Get()'(和'Set()')的所有(并发)调用将会阻止,如果当SomeOtherFunc()无论是由'Get()'还是'Set()'启动' – LWimsey

+0

再次感谢,这正是我所需要的 –

1

你可能想使用future

类模板的std ::未来提供了一种机制来访问异步操作的结果: 异步操作(通过标准::异步,性病:: packaged_task,或std ::许创建)可以提供一个std :: future对象到该异步操作的创建者。 异步操作的创建者然后可以使用各种方法来查询,等待或从std :: future中提取值。如果异步操作尚未提供值,则这些方法可能会阻塞。 当异步操作准备好将结果发送给创建者时,它可以通过修改链接到创建者的std :: future的共享状态(例如std :: promise :: set_value)来完成此操作。