zoukankan      html  css  js  c++  java
  • EC笔记:第三部分:14、在资源管理类中小心Copying行为

    1. 场景

    上一节实现了智能指针,其中的拷贝构造函数和赋值运算符是通过增加/减少指针的引用计数来操作的。但是如果是管理一个独占资源呢?我们希望在一个资源使用时被锁定,在使用完毕后被释放。

    #include <mutex>

    #include <thread>

    #include <iostream>

    using namespace std;

    mutex mu;

    int rc=5;

    void thread1(){

        //mu.lock();

        rc+=5;

        cout<<"thread1:"<<rc<<endl;

        //mu.unlock();

    }

    void thread2(){

        //mu.lock();

        rc-=5;

        cout<<"thread2:"<<rc<<endl;

        //mu.unlock();

    }

     

    int main(){

        thread th1(thread1);

        thread th2(thread2);

        th1.join();

        th2.join();

    }

    在这里,我先把互斥代码去掉,编译运行后的结果是:

    C:UsersSkyFireDesktop>g++ temp.cpp -std=c++11

     

    C:UsersSkyFireDesktop>a

    thread1:thread2:510

     

     

    C:UsersSkyFireDesktop>a

    thread1:thread2:105

     

     

    C:UsersSkyFireDesktop>a

    thread1:thread2:105

     

     

    C:UsersSkyFireDesktop>a

    thread1:thread2:510

     

     

    每次的结果都不确定,因为没加互斥。

    那么,把互斥加上:

    #include <mutex>

    #include <thread>

    #include <iostream>

    using namespace std;

    mutex mu;

    int rc=5;

    void thread1(){

        mu.lock();

        rc+=5;

        cout<<"thread1:"<<rc<<endl;

        mu.unlock();

    }

    void thread2(){

        mu.lock();

        rc-=5;

        cout<<"thread2:"<<rc<<endl;

        mu.unlock();

    }

     

    int main(){

        thread th1(thread1);

        thread th2(thread2);

        th1.join();

        th2.join();

    }

    编译运行的结果是:

    C:UsersSkyFireDesktop>g++ temp.cpp -std=c++11

     

    C:UsersSkyFireDesktop>a

    thread1:10

    thread2:5

     

    C:UsersSkyFireDesktop>a

    thread1:10

    thread2:5

     

    C:UsersSkyFireDesktop>a

    thread1:10

    thread2:5

     

    但是某些时候,我们可能会将unlock的动作漏写(百密一疏),如下面这种:

    #include <mutex>

    #include <thread>

    #include <iostream>

    using namespace std;

    mutex mu;

    int rc=5;

    void thread1(){

        mu.lock();

        rc+=5;

        cout<<"thread1:"<<rc<<endl;

        //mu.unlock();

    }

    void thread2(){

        mu.lock();

        rc-=5;

        cout<<"thread2:"<<rc<<endl;

        mu.unlock();

    }

     

    int main(){

        thread th1(thread1);

        thread th2(thread2);

        th1.join();

        th2.join();

    }

    这样的结果就是thread2里面的语句一直得不到执行,程序死锁。

    编译运行:

    C:UsersSkyFireDesktop>g++ temp.cpp -std=c++11

     

    C:UsersSkyFireDesktop>a

    thread1:10

    ^C

    C:UsersSkyFireDesktop>

     

    可以看到,thread2一直没有执行,后面的^C是我使用Ctrl+C中断的结果。

     

    为了避免这种情况,我们使用资源管理类。

     

    1. 简单的实现

    一个简单的实现:

    class AutoMutex{

        private:

        mutex &mu;

        public:

            AutoMutex(mutex &t):mu(t){

                mu.lock();

            }

            ~AutoMutex(){

                mu.unlock();

            }

    };

     

    这个类在构造的时候会将一个互斥量锁定,而在析构时会释放掉这个互斥量。乍一看好像没什么问题。事实上,在"正常的"情况下,这段代码可以工作的很好。

     

    mutex mu;

     

    void mythread(){

        AutoMutex t(mu);

        cout<<"hello world"<<endl;

    }

     

    int main(){

        for(int i=0;i<10;++i)

            thread(mythread).detach();

        system("pause");

    }

    输出:

     

    C:UsersSkyFireDesktop>g++ temp.cpp -std=c++11

     

    C:UsersSkyFireDesktop>a

    hello world

    hello world

    hello world

    hello world

    hello world

    hello world

    hello world

    hello world

    hello world

    hello world

    请按任意键继续. . .

     

    1. 问题

    但是,如果出现一些比较调皮的程序员(暂定为小明吧)。

    调皮的小明写出了如下的代码:

    mutex mu;

    mutex mu2;

     

    void mythread(){

        AutoMutex t(mu);

        AutoMutex t2(mu2);

        t2=t;

        cout<<"hello world"<<endl;

    }

     

    int main(){

        for(int i=0;i<10;++i)

            thread(mythread).detach();

        system("pause");

    }

    这TM就尴尬了……小明将管理了两个不同的mutex的对象相互赋值了。不过还好,这段代码是编译不通过的(小明的奸计未能得逞)。因为mutex类是不允许复制的,他的赋值运算符是删除的。(假设mutex可以复制,会产生什么?)

    而且,管理两个mutex的对象的赋值没有任何意义,这个对象就是创建与销毁,并没有其他任何作用,所以,对于这个类,只要简单地把拷贝构造函数和赋值运算符屏蔽就好了:

    class AutoMutex{

        private:

        const AutoMutex& operator=(const AutoMutex&)=delete;

        AutoMutex(const AutoMutex&)=delete;

        mutex &mu;

        public:

            AutoMutex(mutex &t):mu(t){

                mu.lock();

            }

            ~AutoMutex(){

                mu.unlock();

            }

    };

    为了应对本宝宝的机智,小明又写出下面这段代码:

    mutex mu;

     

    void mythread(){

        AutoMutex t(mu);

        AutoMutex t2(mu);

        cout<<"hello world"<<endl;

    }

     

    int main(){

        for(int i=0;i<10;++i)

            thread(mythread).detach();

        system("pause");

    }

    不得不说,小明是很奸诈的~~~

    一个互斥锁,对于一个线程来说,只有获取和没获取两种状态,而不存在获取两次这种状态。而不存在什么获取多次什么的状态。

    我们先看一下,对于mutex,获取多次是个什么结果:

    mutex mu;

     

    void mythread(){

        mu.lock();

        mu.lock();

        cout<<"hello world"<<endl;

        mu.unlock();

        mu.unlock();

    }

     

    int main(){

        for(int i=0;i<10;++i)

            thread(mythread).detach();

        system("pause");

    }

    运行结果:

    C:UsersSkyFireDesktop>g++ temp.cpp -std=c++11

     

    C:UsersSkyFireDesktop>a

    请按任意键继续. . .

     

    既然mutex本身就是这么设计的,我们还是不改的好~~~

    猜想mutex这样设计是为了提供PV锁机制:

    下面这段代码,不加任何互斥:

    int main(){

        cout<<1<<endl;

        thread([](){cout<<3<<endl;}).detach();

        cout<<2<<endl;

        thread([](){cout<<4<<endl;}).detach();

        cout<<5<<endl;

    }

    输出结果为:

     

    C:UsersSkyFireDesktop>g++ temp.cpp -std=c++11

     

    C:UsersSkyFireDesktop>a

    1

    3

    2

    54

     

    完全没有顺序可言,但是如果加上一些互斥。

    mutex mu;

     

    int main(){

        cout<<1<<endl;

        thread([](){cout<<3<<endl;mu.unlock();}).detach();

        mu.lock();

        cout<<2<<endl;

        mu.lock();

        thread([](){cout<<4<<endl;mu.unlock();}).detach();

        mu.lock();

        cout<<5<<endl;

        mu.unlock();

    }

    此时的输出结果为:

    C:UsersSkyFireDesktop>g++ temp.cpp -std=c++11

     

    C:UsersSkyFireDesktop>a

    1

    2

    3

    4

    5

     

    Perfect!!!

    这正是mutex为我们提供的特性,既然我们是管理mutex,我们就不该破坏这种特性。

    于是~~~上面全是小明的错^_^。

     

    这里实现的只是对mutex对象的管理,采用了禁止拷贝的方式,但是对其他对象的管理就不一定了,要根据对象的特性灵活管理。

    常见的拷贝行为有:禁止拷贝(例如本类)、引用计数(例如上节的智能指针),但是要记住,如果实现了拷贝,一定要将所有元素全部拷贝。

  • 相关阅读:
    Linux下Maven的安装与使用
    Vue1.0用法详解
    一个异步访问redis的内存问题
    jquery和zepto的异同
    我的学习归纳方法(以学习Maven为例)
    最显而易见的设计最容易成功
    Linux Command Backup
    Turn and Stun server · J
    Android apk签名详解——AS签名、获取签名信息、系统签名、命令行签名
    Leetcode 981. Time Based Key-Value Store(二分查找)
  • 原文地址:https://www.cnblogs.com/SkyFireITDIY/p/6213222.html
Copyright © 2011-2022 走看看