2013-07-24 169 views
4

我想使用C++ 11 std :: condition_variable,但是當我嘗試鎖定與第二個線程關聯的unique_lock時,我得到一個異常「避免資源死鎖」。創建它的線程可以鎖定和解鎖它,但不是第二個線程,儘管我非常確定unique_lock不應該在第二個線程試圖鎖定它的時候鎖定。鎖定C++ 11 std :: unique_lock導致死鎖異常

FWIW我在Linux中使用gcc 4.8.1,使用-std = gnu ++ 11。

我已經在condition_variable,unique_lock和mutex周圍編寫了一個包裝類,因此我的代碼中沒有其他東西可以直接訪問它們。注意使用std :: defer_lock,我已經陷入了陷阱:-)。

class Cond { 
private: 
    std::condition_variable cCond; 
    std::mutex cMutex; 
    std::unique_lock<std::mutex> cULock; 
public: 
    Cond() : cULock(cMutex, std::defer_lock) 
    {} 

    void wait() 
    { 
     std::ostringstream id; 
     id << std::this_thread::get_id(); 
     H_LOG_D("Cond %p waiting in thread %s", this, id.str().c_str()); 
     cCond.wait(cULock); 
     H_LOG_D("Cond %p woke up in thread %s", this, id.str().c_str()); 
    } 

    // Returns false on timeout 
    bool waitTimeout(unsigned int ms) 
    { 
     std::ostringstream id; 
     id << std::this_thread::get_id(); 
     H_LOG_D("Cond %p waiting (timed) in thread %s", this, id.str().c_str()); 
     bool result = cCond.wait_for(cULock, std::chrono::milliseconds(ms)) 
       == std::cv_status::no_timeout; 
     H_LOG_D("Cond %p woke up in thread %s", this, id.str().c_str()); 
     return result; 
    } 

    void notify() 
    { 
     cCond.notify_one(); 
    } 

    void notifyAll() 
    { 
     cCond.notify_all(); 
    } 

    void lock() 
    { 
     std::ostringstream id; 
     id << std::this_thread::get_id(); 
     H_LOG_D("Locking Cond %p in thread %s", this, id.str().c_str()); 
     cULock.lock(); 
    } 

    void release() 
    { 
     std::ostringstream id; 
     id << std::this_thread::get_id(); 
     H_LOG_D("Releasing Cond %p in thread %s", this, id.str().c_str()); 
     cULock.unlock(); 
    } 
}; 

我的主線程創建了一個RenderContext,它有一個與它關聯的線程。從主線程的角度來看,它使用Cond來指示渲染線程執行一個動作,並且還可以在COnd上等待渲染線程完成該動作。渲染線程在Cond上等待主線程發送渲染請求,並使用相同的Cond告訴主線程完成了必要的操作。我得到的錯誤發生在渲染線程試圖鎖定Cond以檢查/等待渲染請求時,此時它不應該被鎖定(因爲主線程正在等待它),更不用說由同一個線程。下面是輸出:

DEBUG: Created window 
DEBUG: OpenGL 3.0 Mesa 9.1.4, GLSL 1.30 
DEBUG: setScreen locking from thread 140564696819520 
DEBUG: Locking Cond 0x13ec1e0 in thread 140564696819520 
DEBUG: Releasing Cond 0x13ec1e0 in thread 140564696819520 
DEBUG: Entering GLFW main loop 
DEBUG: requestRender locking from thread 140564696819520 
DEBUG: Locking Cond 0x13ec1e0 in thread 140564696819520 
DEBUG: requestRender waiting 
DEBUG: Cond 0x13ec1e0 waiting in thread 140564696819520 
DEBUG: Running thread 'RenderThread' with id 140564575180544 
DEBUG: render thread::run locking from thread 140564575180544 
DEBUG: Locking Cond 0x13ec1e0 in thread 140564575180544 
terminate called after throwing an instance of 'std::system_error' 
    what(): Resource deadlock avoided 

說實話,我真的不明白一個unique_lock是什麼,爲什麼需要condition_variable一個,而不是直接使用互斥體,所以這可能是問題的原因。我無法在網上找到很好的解釋。

+3

不要爲所有線程使用相同的'unique_lock',這並不意味着它會被使用。將它們用作塊範圍內的RAII對象,而不是類成員。這樣,每個調用函數的線程都將擁有自己的實例。另外,介紹虛假喚醒。 – syam

+0

我看到了,所以想要等待或發送通知的每個上下文應該使用它自己的unique_lock,但是所有共享相同的互斥量? – realh

+0

只需等待,不發送('cv.notify()'不需要鎖)。但是,否則,是的。我會盡量整理一個答案,告訴你如何正確使用這一點,現在我只是有點忙。 – syam

回答

7

前言:要理解條件變量,重要的是他們可能會受到隨機的,虛假的喚醒。換句話說,簡歷可以從wait()退出,沒有任何人先撥打notify_*()。不幸的是,沒有辦法區分這種虛假的喚醒與合法的喚醒,所以唯一的解決方案是有一個額外的資源(至少是一個布爾值),以便您可以判斷喚醒條件是否實際滿足。

這個額外的資源也應該由一個互斥體來保護,通常和你作爲CV的同伴一樣。


一個CV /互斥體對的典型用法如下:

std::mutex mutex; 
std::condition_variable cv; 
Resource resource; 

void produce() { 
    // note how the lock only protects the resource, not the notify() call 
    // in practice this makes little difference, you just get to release the 
    // lock a bit earlier which slightly improves concurrency 
    { 
     std::lock_guard<std::mutex> lock(mutex); // use the lightweight lock_guard 
     make_ready(resource); 
    } 
    // the point is: notify_*() don't require a locked mutex 
    cv.notify_one(); // or notify_all() 
} 

void consume() { 
    std::unique_lock<std::mutex> lock(mutex); 
    while (!is_ready(resource)) 
     cv.wait(lock); 
    // note how the lock still protects the resource, in order to exclude other threads 
    use(resource); 
} 

相比,你的代碼,請注意線程如何將幾個可以同時調用produce()/consume()而不必擔心共享unique_lock:唯一共享事情是mutex/cv/resource,每個線程都有自己的unique_lock,如果互斥鎖已被別的東西鎖住,強制線程等待。

正如你所看到的,資源不能真正與CV/mutex對分開,這就是爲什麼我在評論中說你的包裝類並不真正適合IMHO,因爲它確實試圖分離它們。

通常的做法不是像你試圖的那樣爲CV/mutex對創建一個包裝,而是整個CV/mutex /資源三重組的。例如。一個線程安全的消息隊列,消費者線程將在CV上等待,直到隊列中的消息準備好被消耗。


如果你真的想包裝只是CV /互斥體對,你應該擺脫你的lock()/release()方法,這是不安全的(從一個RAII點),並用一個單一的lock()方法返回替換它們一個unique_ptr

std::unique_ptr<std::mutex> lock() { 
    return std::unique_ptr<std::mutex>(cMutex); 
} 

這樣您就可以使用您的Cond包裝類中,而相同的方式,就是我上面顯示:

Cond cond; 
Resource resource; 

void produce() { 
    { 
     auto lock = cond.lock(); 
     make_ready(resource); 
    } 
    cond.notify(); // or notifyAll() 
} 

void consume() { 
    auto lock = cond.lock(); 
    while (!is_ready(resource)) 
     cond.wait(lock); 
    use(resource); 
} 

但說實話,我不確定它是否值得麻煩:如果您想使用recursive_mutex而不是簡單的mutex?那麼,你必須從你的類中創建一個模板,以便你可以選擇互斥體類型(或者完全寫出第二個類,以便代碼複製)。無論如何,由於您仍然需要編寫幾乎相同的代碼才能管理資源,因此收益並不大。一個僅用於CV /互斥對的包裝類太薄了一個包裝,真正有用的恕我直言。但像往常一樣,YMMV。

+0

感謝您的詳細解答。但是,在調用cv.wait()時,您是否必須傳遞對unique_lock的引用? – realh

+0

哎呀你是對的,看起來我有點被帶走了。 :)現在解決這個問題。 – syam

+0

我使用包裝類的原因是因爲我正在編寫一些可移植的框架代碼。我最初將在Windows和Linux上使用SDL的線程API,並且在Android中將所有線程管理都留給Java或使用pthread。我並不是100%確信C++ 11支持在我可能願意支持的每個平臺上都很穩定,但希望它可以。 – realh