1. 程式人生 > >當建構函式洩露this指標時

當建構函式洩露this指標時

            當一個類正在構造時在建構函式中將this洩露給了其它物件,這在單執行緒序列執行情況下可能沒有什麼問題,但是在多執行緒下那麼問題就比較大了。比如執行緒1負責構造這個物件A但是在建構函式中將this指標洩露給了其它執行緒所呼叫的物件B,不巧的是其它執行緒所呼叫的物件B看見A有些不爽將其析構了。那麼最後A自以為一切構造好了返回,執行緒1然後對這個A操作,最後可怕的錯誤(比如段錯誤)無窮無盡的折磨執行緒1......

           模擬這個問題:假設有一個人A,餐館B,A沒錢就去餐館B點菜吃飯,單執行緒模式下:A進入餐館點菜->餐館B炒菜(不辭辛勞)->A吃完了沒錢付->餐館B大為光火.....此後出現了一幕CATV不會報道的事件.......

           但是聰明的餐館B現在學聰明瞭,也會利用高科技了,利用某種技術可以檢測到使用者是否有錢(比如和銀行通姦哈哈)從而開啟了個後臺程序檢測客戶沒錢立馬轟走.....那麼問題變成如下:

           執行緒1:客人A進入餐館B點菜,點菜這個操作這裡強加為:A將this指標洩露給了B

           執行緒2:餐館B發現有客人來了->獲取該客人的資訊(這裡是this指標)->通過科學技術發現客戶沒錢->不準廚房炒菜了,立馬暴打客戶一頓

           在這種模式下顯然客戶不可能吃完飯再捱揍了.....這就是洩露this指標的可怕之處,我還沒有構造好,其它執行緒就把我給幹掉了....我以後還咋活啊???

#include<iostream>
#include<unistd.h>
#include<pthread.h>
#include<assert.h>
using namespace std;
class Hotel;//餐館
class Person{//人
    public:
        Person(int a,Hotel* h);//a是代表人身上多少錢,h是吃飯的餐館
        int  get(){
            return *money;
        }
        ~Person();
    private:
        int* money;
};
class Hotel{//餐館
    public:
        Hotel():flag(false),p(NULL){}
        void check(){//餐館檢查客人是不是有錢
            if(flag&&p->get()<=0){//flag用於是否有客人,p是客人若客人的錢get()小於等於0表示沒錢要吃霸王餐啊,果斷轟出去
                p->~Person();//幹掉這個吃霸王餐的人
            }
        }
        void _register(Person* a){//註冊是否有客人來,這個函式交給Person類即客人來了就告訴餐館我來了
            flag=true;
            p=a;
        }
    private:
        bool flag;
        Person* p;
};
Hotel* h=new Hotel;
Person::Person(int a,Hotel* h){
    money=new int(a);
    h->_register(this);//客人向餐館註冊
    sleep(3);//休眠的原因是讓建構函式在此停止,造成正在構造的情形(正在等菜....),那麼餐館的執行緒就有時間檢查客人是不是有錢
}
Person::~Person(){
    delete money;
}
void* worker1(void* arg){//客人執行緒
    Person* temp=(Person*)arg;
    temp=new Person(-10,h);//客人temp欠債10塊(這裡-10的money可以理解為一張催款單)還進了餐館吃飯
    cout<<temp->get()<<endl;//客人看下催款單還在不在...我還要去繳費呢...
}
void* worker2(void* arg){//餐館工作執行緒
    Person* p=(Person*)arg;//
    sleep(1);//睡眠1s是為了讓客人進入建構函式但是又沒有離開建構函式
    h->check();//在客人還沒有構造完成時(比如正在等菜時...)立刻檢查客人是否有錢,很不幸的是餐館發現客人的催款單一怒把催款單銷燬delete了,並欲把客人轟出去
}
int main(){
    pthread_t pthd[2];
    Person* one;//客人
    int ret=pthread_create(&pthd[0],NULL,worker1,one);//客人執行緒
    assert(ret==0);
    ret=pthread_create(&pthd[1],NULL,worker2,h);//餐館執行緒
    assert(ret==0);
    ret=pthread_join(pthd[0],NULL);
    assert(ret==0);
    ret=pthread_join(pthd[1],NULL);
    assert(ret==0);
    return 0;
}
輸出結果:

0        //本來客戶預期是點完菜還要做其他事呢,可是餐館發現他沒錢立馬暴打他並轟走他....想吃飽飯再捱打不可能了....

總結:

物件構造實現執行緒安全原則是:不要在構造期間洩露this指標即:不要在建構函式中註冊回撥函式,不要在建構函式中把this指標傳給跨執行緒的物件,即使在建構函式最後一行也不行(比如基類A,子類B先構造基類A,A在構造最後一行向其它物件C註冊了什麼而B正在構造...,C拿著A的虛擬函式執行什麼關於B的操作....發生了錯誤)。 不然讓別的執行緒看到這個半成品物件會幹出什麼事誰也不知道(比如廢掉物件)....那麼那些註冊回撥函式的操作怎麼辦?不寫在建構函式中,還可以寫在其它成員函式中嘛,只不過使用起來多了次成員函式呼叫罷了。為了安全,你懂的....