[c++11]多線程編程(四)——死鎖(Dead Lock)

死鎖

若是你將某個mutex上鎖了,卻一直不釋放,另外一個線程訪問該鎖保護的資源的時候,就會發生死鎖,這種狀況下使用lock_guard能夠保證析構的時候可以釋放鎖,然而,當一個操做須要使用兩個互斥元的時候,僅僅使用lock_guard並不能保證不會發生死鎖,以下面的例子:ios

#include <iostream>
#include <thread>
#include <string>
#include <mutex>
#include <fstream>
using namespace std;

class LogFile {
    std::mutex _mu;
    std::mutex _mu2;
    ofstream f;
public:
    LogFile() {
        f.open("log.txt");
    }
    ~LogFile() {
        f.close();
    }
    void shared_print(string msg, int id) {
        std::lock_guard<std::mutex> guard(_mu);
        std::lock_guard<std::mutex> guard2(_mu2);
        f << msg << id << endl;
        cout << msg << id << endl;
    }
    void shared_print2(string msg, int id) {
        std::lock_guard<std::mutex> guard(_mu2);
        std::lock_guard<std::mutex> guard2(_mu);
        f << msg << id << endl;
        cout << msg << id << endl;
    }
};

void function_1(LogFile& log) {
    for(int i=0; i>-100; i--)
        log.shared_print2(string("From t1: "), i);
}

int main()
{
    LogFile log;
    std::thread t1(function_1, std::ref(log));

    for(int i=0; i<100; i++)
        log.shared_print(string("From main: "), i);

    t1.join();
    return 0;
}

運行以後,你會發現程序會卡住,這就是發生死鎖了。程序運行可能會發生相似下面的狀況:c++

Thread A              Thread B
_mu.lock()             _mu2.lock()
   //死鎖               //死鎖
_mu2.lock()         _mu.lock()

解決辦法有不少:編程

  1. 能夠比較mutex的地址,每次都先鎖地址小的,如:併發

    if(&_mu < &_mu2){
        _mu.lock();
        _mu2.unlock();
    }
    else {
        _mu2.lock();
        _mu.lock();
    }
  2. 使用層次鎖,將互斥鎖包裝一下,給鎖定義一個層次的屬性,每次按層次由高到低的順序上鎖。

這兩種辦法其實都是嚴格規定上鎖順序,只不過實現方式不一樣。函數

c++標準庫中提供了std::lock()函數,可以保證將多個互斥鎖同時上鎖,this

std::lock(_mu, _mu2);

同時,lock_guard也須要作修改,由於互斥鎖已經被上鎖了,那麼lock_guard構造的時候不該該上鎖,只是須要在析構的時候釋放鎖就好了,使用std::adopt_lock表示無需上鎖:spa

std::lock_guard<std::mutex> guard(_mu2, std::adopt_lock);
std::lock_guard<std::mutex> guard2(_mu, std::adopt_lock);

完整代碼以下:線程

#include <iostream>
#include <thread>
#include <string>
#include <mutex>
#include <fstream>
using namespace std;

class LogFile {
    std::mutex _mu;
    std::mutex _mu2;
    ofstream f;
public:
    LogFile() {
        f.open("log.txt");
    }
    ~LogFile() {
        f.close();
    }
    void shared_print(string msg, int id) {
        std::lock(_mu, _mu2);
        std::lock_guard<std::mutex> guard(_mu, std::adopt_lock);
        std::lock_guard<std::mutex> guard2(_mu2, std::adopt_lock);
        f << msg << id << endl;
        cout << msg << id << endl;
    }
    void shared_print2(string msg, int id) {
        std::lock(_mu, _mu2);
        std::lock_guard<std::mutex> guard(_mu2, std::adopt_lock);
        std::lock_guard<std::mutex> guard2(_mu, std::adopt_lock);
        f << msg << id << endl;
        cout << msg << id << endl;
    }
};

void function_1(LogFile& log) {
    for(int i=0; i>-100; i--)
        log.shared_print2(string("From t1: "), i);
}

int main()
{
    LogFile log;
    std::thread t1(function_1, std::ref(log));

    for(int i=0; i<100; i++)
        log.shared_print(string("From main: "), i);

    t1.join();
    return 0;
}

總結一下,對於避免死鎖,有如下幾點建議:code

  1. 建議儘可能同時只對一個互斥鎖上鎖。資源

    {
        std::lock_guard<std::mutex> guard(_mu2);
        //do something
        f << msg << id << endl;
    }
    {
        std::lock_guard<std::mutex> guard2(_mu);
        cout << msg << id << endl;
    }
  2. 不要在互斥鎖保護的區域使用用戶自定義的代碼,由於用戶的代碼可能操做了其餘的互斥鎖。

    {
        std::lock_guard<std::mutex> guard(_mu2);
        user_function(); // never do this!!!
        f << msg << id << endl;
    }
  3. 若是想同時對多個互斥鎖上鎖,要使用std::lock()
  4. 給鎖定義順序(使用層次鎖,或者比較地址等),每次以一樣的順序進行上鎖。詳細介紹可看C++併發編程實戰

參考

  1. C++併發編程實戰
  2. C++ Threading #4: Deadlock
相關文章
相關標籤/搜索