C++并发实战8:deadlock

         1 死锁:每个线程都希望锁住一些列锁以执行某个操作,且每个线程持有一个不同的锁,最终每个线程都需要其它线程的锁,导致所有线程都不能向前执行。


          1.1  顺序加锁lock

                1.1 死锁的一个解决方式:每个线程对锁的请求顺序一致。C++库提供了顺序加锁机制,可以一次性锁住多个mutex而不会出现死锁:

void lock (Mutex1& a, Mutex2& b, Mutexes&... cde);
           对a,b...cde锁顺序加锁,若在加锁过程中抛出任何异常就释放前面已持有的锁。不会出现死锁,一个例子如下:

#include <mutex>
#include<unistd.h>
#include<thread>
#include<iostream>
using namespace std;
class big_object
{
    public:
        big_object(int i=0):data(i){}
    public:
        int data;
};

void swap(big_object& lhs,big_object& rhs)
{
    sleep(1);
    cout<<"swap()"<<endl;
}
class X
{
    private:
        big_object some_detail;
        mutable std::mutex m;
    public:
        X(big_object const& sd):some_detail(sd){}

        friend void swap(X& lhs, X& rhs)
        {
            if(&lhs==&rhs)
                return;
            std::lock(lhs.m,rhs.m);//C++库会自动生成加锁顺序,即使调用顺序不一致
            std::lock_guard<std::mutex> lock_a(lhs.m,std::adopt_lock);//adopt_lock是告诉lock_guard对象mutex已经被上锁,而lock_gurad对象将获得mutex的所有权,这样就可以保证在lock可能出现异常导致没有unlock的情形不会出现,栈对象会在异常抛出后自动析构
            std::lock_guard<std::mutex> lock_b(rhs.m,std::adopt_lock);
            swap(lhs.some_detail,rhs.some_detail);
        }
};
void threadFun(X& one,X& two){
    swap(one,two);
}
int main()
{
    big_object ten(10),hundred(100);
    X one(ten),two(hundred);
    thread threadOne(threadFun,ref(one),ref(two));//不同线程有不同的参数调用顺序
    thread threadTwo(threadFun,ref(two),ref(one));
    threadOne.join();
    threadTwo.join();
    return 0;
}

程序输出:

swap()

swap()


     
           

            1.2 可以通过比较多个mutex的地址大小实现顺序加锁,下面的程序是先锁地址小的mutex,再锁地址大的mutex。

#include<iostream>
#include<mutex>
#include<unistd.h>
#include<thread>
#include<functional>
using namespace std;
class test{
    public:
        friend void orderLock(test& one,test& two){
            if(&one.m>&two.m){
                lock_guard<mutex> smallGuard(two.m);
                sleep(1);
                cout<<"the second argument's mutex smaller"<<endl;
                lock_guard<mutex> bigGuard(one.m);
            }
            else if(&one.m==&two.m)
                cout<<"lock the same mutex"<<endl;
            else{
                lock_guard<mutex> smallGuard(one.m);
                sleep(1);
                cout<<"the first argument's mutex smaller"<<endl;
                lock_guard<mutex> bigGuard(two.m);
            }
        }
    private:
        mutex m;
};
//void threadFun(test& one,test& two){//#2#
//    orderLock(one,two);
//    cout<<&one<<" "<<&two<<" lock success"<<endl;
//}
int main(){
    test one,two;
    cout<<&one<<" "<<&two<<endl;
    orderLock(one,two);//#1#
    orderLock(two,one);//#1#
    //thread threadOne(threadFun,ref(one),ref(two));//#2#   注意这里要用ref将test对象转换为引用传递到子线程中去,否则thread会拷贝test对象从而拷贝mutex,而mutex::mutex(const mutex&)=delete从而会报错
    //thread threadTwo(threadFun,ref(two),ref(one));//#2#
    //threadOne.join();
    //threadTwo.join();//这里要说明的是多线程间共享锁不是什么好事情
    return 0;
}


注释掉#1#,采用#2#多线程模拟,程序输出:多线程下证地址小的mutex先上锁的加锁顺序
the first argument's mutex smaller //orderLock(one,two)先锁one
the second argument's mutex smaller //orderLock(two,one)先锁one
0x7fffa4791500 0x7fffa4791530
the first argument's mutex smaller
0x7fffa4791500 0x7fffa4791530 lock success
the second argument's mutex smaller
0x7fffa4791530 0x7fffa4791500 lock success
0x7fffae7c2940 0x7fffae7c2970
也是保



2 避免死锁
        死锁不仅仅发生在mutex上,如多个线程相互之间调用join也会死锁。避免死锁的方式:don’t wait for another thread if there’s a chance it’s waiting for you//不要等待那些有机会等待你的线程。
       2.1 避免使用递归锁,don’t acquire a lock if you already hold one。
       2.2 避免持有锁期间执行用户代码,不清楚用户代码将会做什么就有可能出现死锁。有时这种情形难以避免的,如std::stack是个泛型类,内部存储用户指定的数据类型,stack<vector<string> >之类的vector本身就具有很多操作,更别说stack存储用户自定义的类万一又想存取stack本身呢?这种情形可以强加一些限制条件以保证加锁安全。
        2.3 若果一定要使用两个及以上的加锁,一定要确保加锁顺序,std::lock是个不错的选择。例如一个双向链表若一个线程从头开始遍历,一个线程从尾开始遍历那么两个线程必然会在中间某处出现死锁,这时放弃双向遍历而规定一个加锁顺序。
        2.4 lock hierarchy: 通常对对个mutex加锁会导致死锁,解决这个问题的最好办法就是保证加锁的顺序,当lock总是以一个规定的顺序进行时不会出现死锁。lock hierarchy指的是给每个mutex分配一个标号从而对mutex逻辑排序。限制条件是:当线程已经持有编号比n小的锁时不能再请求标号为n的mutex.

#include <mutex>
#include <stdexcept>
class hierarchical_mutex//可用于lock_guard<hierarchical_mutex>
{
    std::mutex internal_mutex;//
    unsigned long const hierarchy_value;//mutex所在的层次
    unsigned long previous_hierarchy_value;//记录前一个mutex的层次,用于解锁时恢复线程的层次
    static thread_local unsigned long this_thread_hierarchy_value;//线程所在的层次,是个线程私有数据

    void check_for_hierarchy_violation()//检查当前mutex是否小于线程层次,不是则抛出异常
    {
        if(this_thread_hierarchy_value <= hierarchy_value)
        {
            throw std::logic_error("mutex hierarchy violated");
        }
    }
    void update_hierarchy_value()//更新线程的层次
    {
        previous_hierarchy_value=this_thread_hierarchy_value;//通过previous_hierarchy_value记住线程的层次
        this_thread_hierarchy_value=hierarchy_value;//用当前mutex的层次更新线程层次
    }
public:
    explicit hierarchical_mutex(unsigned long value):
        hierarchy_value(value),//mutex层次初始值
        previous_hierarchy_value(0)
    {}
    void lock()//对mutex加锁
    {
        check_for_hierarchy_violation();//先检查,保证mutex层次小于线程层次
        internal_mutex.lock();
        update_hierarchy_value();//更新线程层次
    }
    void unlock()//对mutex解锁
    {
        this_thread_hierarchy_value=previous_hierarchy_value;//用记录的previous_hierarchy_value恢复线程层次
        internal_mutex.unlock();
    }
    bool try_lock()//尝试加锁,若mutex已被其它上锁则返回false
    {
        check_for_hierarchy_violation();
        if(!internal_mutex.try_lock())
            return false;
        update_hierarchy_value();
        return true;
    }
};
thread_local unsigned long
    hierarchical_mutex::this_thread_hierarchy_value(ULONG_MAX);//线程层次初始值为最大,保证开始可以对任意mutex上锁   
















   

你可能感兴趣的:(C++并发实战8:deadlock)