EC筆記:第三部分:1四、在資源管理類中當心Copying行爲

  1. 場景

上一節實現了智能指針,其中的拷貝構造函數和賦值運算符是經過增長/減小指針的引用計數來操做的。可是若是是管理一個獨佔資源呢?咱們但願在一個資源使用時被鎖定,在使用完畢後被釋放。 ios

#include <mutex> c++

#include <thread> 程序員

#include <iostream> 函數

using namespace std; spa

mutex mu; 線程

int rc=5; 設計

void thread1(){ 指針

    //mu.lock(); c++11

    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:\Users\SkyFire\Desktop>g++ temp.cpp -std=c++11

 

C:\Users\SkyFire\Desktop>a

thread1:thread2:510

 

 

C:\Users\SkyFire\Desktop>a

thread1:thread2:105

 

 

C:\Users\SkyFire\Desktop>a

thread1:thread2:105

 

 

C:\Users\SkyFire\Desktop>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:\Users\SkyFire\Desktop>g++ temp.cpp -std=c++11

 

C:\Users\SkyFire\Desktop>a

thread1:10

thread2:5

 

C:\Users\SkyFire\Desktop>a

thread1:10

thread2:5

 

C:\Users\SkyFire\Desktop>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:\Users\SkyFire\Desktop>g++ temp.cpp -std=c++11

 

C:\Users\SkyFire\Desktop>a

thread1:10

^C

C:\Users\SkyFire\Desktop>

 

能夠看到,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:\Users\SkyFire\Desktop>g++ temp.cpp -std=c++11

 

C:\Users\SkyFire\Desktop>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:\Users\SkyFire\Desktop>g++ temp.cpp -std=c++11

 

C:\Users\SkyFire\Desktop>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:\Users\SkyFire\Desktop>g++ temp.cpp -std=c++11

 

C:\Users\SkyFire\Desktop>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:\Users\SkyFire\Desktop>g++ temp.cpp -std=c++11

 

C:\Users\SkyFire\Desktop>a

1

2

3

4

5

 

Perfect!!!

這正是mutex爲咱們提供的特性,既然咱們是管理mutex,咱們就不應破壞這種特性。

因而~~~上面全是小明的錯^_^。

 

這裏實現的只是對mutex對象的管理,採用了禁止拷貝的方式,可是對其餘對象的管理就不必定了,要根據對象的特性靈活管理。

常見的拷貝行爲有:禁止拷貝(例如本類)、引用計數(例如上節的智能指針),可是要記住,若是實現了拷貝,必定要將全部元素所有拷貝。

相關文章
相關標籤/搜索