2012-11-20 72 views
7

我一直在尋找合併一段數據的方法,這些數據將被多個線程訪問,並與爲線程安全提供的鎖一起使用。我認爲我已經達到了一個我認爲不可能做到這一點的地步,同時保持了const-correctness。組合數據和鎖定時不可能是常量正確的?

採取以下類,例如:

template <typename TType, typename TMutex> 
class basic_lockable_type 
{ 

public: 
    typedef TMutex lock_type; 

public: 
    template <typename... TArgs> 
    explicit basic_lockable_type(TArgs&&... args) 
     : TType(std::forward<TArgs...>(args)...) {} 

    TType& data() { return data_; } 
    const TType& data() const { return data_; } 

    void lock() { mutex_.lock(); } 
    void unlock() { mutex_.unlock(); } 

private: 
    TType   data_; 
    mutable TMutex mutex_; 

}; 

typedef basic_lockable_type<std::vector<int>, std::mutex> vector_with_lock; 

在此我嘗試將數據和鎖定結合起來,標誌着mutex_mutable。不幸的是,這是不夠的,因爲我看到它,因爲使用時,vector_with_lock將不得不被標記爲mutable,以便從const函數執行讀取操作,該函數不完全正確(data_應該是來自const的mutable) 。

void print_values() const 
{ 
    std::lock_guard<vector_with_lock> lock(values_); 
    for(const int val : values_) 
    { 
     std::cout << val << std::endl; 
    } 
} 

vector_with_lock values_; 

任何人都可以看到這樣的話,這樣const-correctness維護,同時結合數據和鎖嗎?另外,我在這裏做了任何不正確的假設嗎?

+2

使'lock'和'unlock'爲const? (另外,它不應該是'std :: lock_guard '?爲什麼你會新建一個可鎖定的,如果你不打算使用它?) –

+0

更新'lock_guard' – Graeme

+0

@ R.MartinhoFernandes當然,標記這些const會允許一個不可變的'vector_with_lock'實例調用'lock'和'unlock',是嗎? – Graeme

回答

6

就個人而言,我更喜歡一種不需要手動鎖定的設計,並且數據可以正確地封裝,而不必先鎖定才能實際訪問它。

其中一種選擇是讓朋友功能apply或者做鎖定的東西,抓取封裝的數據並將其傳遞給一個函數對象,該對象與其中的鎖一起運行。

//! Applies a function to the contents of a locker_box 
/*! Returns the function's result, if any */ 
template <typename Fun, typename T, typename BasicLockable> 
ResultOf<Fun(T&)> apply(Fun&& fun, locker_box<T, BasicLockable>& box) { 
    std::lock_guard<BasicLockable> lock(box.lock); 
    return std::forward<Fun>(fun)(box.data); 
} 
//! Applies a function to the contents of a locker_box 
/*! Returns the function's result, if any */ 
template <typename Fun, typename T, typename BasicLockable> 
ResultOf<Fun(T const&)> apply(Fun&& fun, locker_box<T, BasicLockable> const& box) { 
    std::lock_guard<BasicLockable> lock(box.lock); 
    return std::forward<Fun>(fun)(box.data); 
} 

用法然後變成:

void print_values() const 
{ 
    apply([](std::vector<int> const& the_vector) { 
     for(const int val : the_vector) { 
      std::cout << val << std::endl; 
     } 
    }, values_); 
} 

或者,可以濫用for循環正常範圍鎖定基於範圍的,並提取該值作爲「單」操作。所有需要的是正確的一組迭代器的:

for(auto&& the_vector : box.open()) { 
    // lock is held in this scope 
    // do our stuff normally 
    for(const int val : the_vector) { 
     std::cout << val << std::endl; 
    } 
} 

我想解釋是爲了。總體思路是open()返回一個RAII句柄,該句柄獲取構造上的鎖定並在銷燬時釋放它。只要該循環執行,基於範圍的for循環將確保這個臨時生命。這給了正確的鎖定範圍。

RAII句柄還提供了begin()end()迭代器的範圍與單個包含值。這是我們如何獲得受保護的數據。基於範圍的循環負責爲我們進行解引用並將其綁定到循環變量。由於範圍是單身,「循環」實際上總是隻運行一次。

box不應該提供任何其他方式來獲取數據,以便它實際上實施互鎖訪問。

當盒子打開後,當盒子關閉後可以使用參考,當然可以收集對數據的引用。但這是爲了防止墨菲,而不是馬基雅維利。

構造看起來很奇怪,所以我不會責怪任何人不想要它。一方面我想用這個,因爲語義是完美的,但另一方面我不想,因爲這不是基於範圍的。在抓握的手這範圍-RAII混合技術是相當通用的,可以很容易地濫用其他目的,但我會留下你的想象力/噩夢;)使用自行決定。


作爲練習留給讀者,但這樣一組迭代器的一個簡單的例子可以在我自己的locker_box implementation被發現。

+0

O_o'for'是相當濫用。 – Mankarse

+0

我剛剛注意到我的實現中有一個錯誤:S Ooops。不要在家中使用該代碼,孩子。 –

+0

它仍然是越野車嗎?如果是這樣,爲什麼? – sehe

3

你對「const correct」有什麼瞭解?一般來說,我認爲邏輯常量是一致的,這意味着如果互斥量不是對象的邏輯(或可觀察)狀態的一部分,則聲明它並沒有什麼錯,甚至在常量函數中也可以使用它。

0

從某種意義上說,無論互斥鎖是否鎖定,都是對象可觀察狀態的一部分 - 例如,您可以通過意外創建鎖定反轉來觀察它。

這是自鎖對象的一個​​基本問題,我猜想它的一個方面確實與常量正確性有關。

您可以通過引用常量來更改對象的「鎖定性」,否則無法通過引用常量來進行同步訪問。選一個,大概是第一個。

另一種方法是確保在處於鎖定狀態時不會由調用代碼「觀察」對象,以便鎖定不屬於可觀察狀態。但是,然後呼叫者無法訪問其vector_with_lock中的每個元素作爲單個同步操作。只要您在持有鎖的情況下調用用戶代碼,他們就可以編寫包含潛在或有保證的鎖定反轉的代碼,以便「查看」該鎖是否被保持。所以對於集合,這不能解決問題。