2017-02-10 81 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)來完成此操作。