临界区重叠:移出临界区+shared_ptr写时拷贝

    死锁例子:

         一些函数看似其本身是线程安全的,但是当多个函数同时执行时出现了非线程安全。一个例子是多个函数在多线程下同时执行时临界区相互重叠

        Requset向Inventory注册,然后Invetory存有Request对象的指针。当Request对象析构自身时向Inventory中移除指针,此时若析构停住,然后Inventory持有该Request指针做一些操作,将访问到Request这个半成品对象。且为了保证每个函数线程安全采用锁保护,最后多个函数同时执行导致死锁。

#include<iostream>
#include<string>
#include<unistd.h>
#include<pthread.h>
#include<set>
using namespace std;
class Request;
class Inventory{
    public:
        void add(Request* req){//添加Request
            pthread_mutex_lock(&mutex);
            requests.insert(req);
            pthread_mutex_unlock(&mutex);
        }
        void remove(Request* req){//移除Request
            pthread_mutex_lock(&mutex);
            requests.erase(req);
            pthread_mutex_unlock(&mutex);
        }
        void printALL();//打印所有Request
        Inventory(){
            pthread_mutex_init(&mutex,NULL);
        }
    private:
        pthread_mutex_t mutex;//保护成员
        set<Request*> requests;//关于Request的容器
};
Inventory g_inventory;
class Request{
    public:
        void process(){//将自己添加到inventory
            pthread_mutex_lock(&mutex);
            g_inventory.add(this);
            pthread_mutex_unlock(&mutex);
        }
        ~Request() __attribute__ ((noinline)){
            pthread_mutex_lock(&mutex);//###2###
            sleep(1);//###1###这里是为了处于析构状态中,下面的###1###出现死锁
            g_inventory.remove(this);//###2###
            pthread_mutex_unlock(&mutex);
        }
        void print(){// __attribute__ ((noninline)){//告诉编译器此函数非内联
            pthread_mutex_lock(&mutex);
            cout<<"Request print()"<<endl;
            pthread_mutex_unlock(&mutex);
        }
    private:
        pthread_mutex_t mutex;
};
void Inventory::printALL(){
    pthread_mutex_lock(&mutex);
    for(set<Request*>::const_iterator it=requests.begin();it!=requests.end();it++){
        (*it)->print();//###1###当Request处于析构中时,这里出现死锁
    }
    cout<<"Inventory::printALL() unlocked"<<endl;
    pthread_mutex_unlock(&mutex);
}
void* threadFun(void* arg){
    Request* req=new Request;
    req->process();
    delete req;
}
int main(){
    pthread_t pid;
    if(pthread_create(&pid,NULL,threadFun,NULL)<0)
        cout<<"pthread_create error"<<endl;
    usleep(500*1000);//###1###这里休眠是为了让Request添加到inventory
    g_inventory.printALL();
    pthread_join(pid,NULL);
    return 0;
}

程序在前两个###1###处出现死锁。


        死锁解决方式1:

             将Request::remove()移除临界区,交换###2###处的两句位置,没有解决析构竞态,当Request::remove()后处于正在析构中,刚好inventory::printALL()访问到这个半废品对象...


       死锁解决方式2:

           死锁出现的原因是无法确定对方的生命周期出现析构竞态,采用shared_ptr/weak_ptr管理对象的生命周期。

#include<iostream>
#include<string>
#include<unistd.h>
#include<pthread.h>
#include<set>
#include<boost/shared_ptr.hpp>
#include<boost/weak_ptr.hpp>
#include<boost/enable_shared_from_this.hpp>
using namespace std;
using namespace boost;
class Request;
class Inventory{
    public:
        void add(shared_ptr<Request> a){
            weak_ptr<Request> one(a);
            pthread_mutex_lock(&mutex);
            requests.insert(one);
            pthread_mutex_unlock(&mutex);
        }
        void remove(shared_ptr<Request> &a){
            weak_ptr<Request> one(a);
            pthread_mutex_lock(&mutex);
            requests.erase(one);
            pthread_mutex_unlock(&mutex);
        }
        void printALL();
        Inventory(){
            pthread_mutex_init(&mutex,NULL);
        }
    private:
        pthread_mutex_t mutex;
        set<weak_ptr<Request> > requests;//持有所有Request对象的weak_ptr
};
Inventory g_inventory;
class Request:public enable_shared_from_this<Request>{
    public:
        void process(){
            pthread_mutex_lock(&mutex);
            g_inventory.add(shared_from_this());//shared_from_this()将this指针转换为shared_ptr指针
            pthread_mutex_unlock(&mutex);
        }
        ~Request() __attribute__ ((noinline)){//告诉编译器此函数非内联
            //pthread_mutex_lock(&mutex);
            //sleep(1);
            //g_inventory.remove(a);
            //pthread_mutex_unlock(&mutex);
            cout<<"~Request()"<<endl;
        }
        void print(){// __attribute__ ((noninline)){
            pthread_mutex_lock(&mutex);
            cout<<"Request print()"<<endl;
            pthread_mutex_unlock(&mutex);
        }
    private:
        pthread_mutex_t mutex;
};
void Inventory::printALL(){
    shared_ptr<Request> one;
    pthread_mutex_lock(&mutex);
    for(set<weak_ptr<Request> >::iterator it=requests.begin();it!=requests.end();it++){
        one=it->lock();//尝试提升weak_ptr,若提升成功则Request对象还存在,否则不存在。注意这个提升操作是线程安全的,这点至关重要
        if(one){
            one->print();
            cout<<"lock success"<<endl;
        }
        else{
            requests.erase(it);
            //it--;//原本以为set会像vector的erase会返回一个迭代器那么需要先自减再自增这样才完全遍历容器,可是set不是那样,可见set底层应该是类似链表这样的数据结构实现的
            cout<<"lock failure"<<endl;
        }
    }
    cout<<"Inventory::printALL() unlocked"<<endl;
    pthread_mutex_unlock(&mutex);
}
void* threadFun(void* arg){
    shared_ptr<Request> one(new Request);//###2###采用shared_ptr管理Request对象生命周期
    one->process();
    sleep(1);//###4###注释掉该句则:Requset对象析构了,Inventory::printALL()无打印对象
    //delete req;
}
int main(){
    pthread_t pid;
    if(pthread_create(&pid,NULL,threadFun,NULL)<0)
        cout<<"pthread_create error"<<endl;
    usleep(500*1000);
    g_inventory.printALL();
    pthread_join(pid,NULL);
    return 0;
}


###4###不注释的输出:

Request print()
lock success
Inventory::printALL() unlocked
~Request()

###4###注释掉的输出:

~Request()
lock failure

注:这种方式存在轻微的内存泄露,当Request对象析构后,Inventory不会立即将相应的weak_ptr从容器中删除,而是到下一次遍历容器时才会删除(延迟删除)。


    死锁解决方式3:

                将print()移除printALL()的临界区,采用写时拷贝技术。一个简单的方法是:在Inventory::printALL()内用一个临时容器temp在临界区内复制requests_全部元素,然后temp在临界区外执行遍历操作。这样显然拷贝整个容器非上策。还有个方法是:采用shared_ptr管理set容器。

               Inventory::add(),Inventory::remove()可以看成针对容器set的写者,而Inventory::printALL()看成是set的读者。采用生产者消费者模型,写者要操作时采用写时拷贝,读者始终在set的一个副本上读取。并将Request析构临界区内的remove操作移出临界区,采用一个函数执行该操作,这样Request对象析构前需要显式调用此函数。

#include<iostream>
#include<string>
#include<unistd.h>
#include<pthread.h>
#include<assert.h>
#include<set>
#include<boost/shared_ptr.hpp>
#include<boost/weak_ptr.hpp>
#include<boost/enable_shared_from_this.hpp>
using namespace std;
using namespace boost;
class Request;
class Inventory{
    public:
        void add(shared_ptr<Request> a){//向Inventory添加一个Request....写者
            weak_ptr<Request> one(a);
            pthread_mutex_lock(&mutex);
            if(!requests.unique()){//采用写时拷贝,若requests有其它读者,则拷贝
                requests.reset(new set<weak_ptr<Request> >(*requests));//强制将requests计数置为1,而读者读取的是读者局部的shared_ptr
                cout<<"add() copy on write"<<endl;
            }
            assert(requests.unique());//判定requests有且仅有一个写者
            requests->insert(one);//添加
            pthread_mutex_unlock(&mutex);
        }
        void remove(shared_ptr<Request> a){//向Inventory移除一个Request...写者
            weak_ptr<Request> one(a);
            pthread_mutex_lock(&mutex);
            if(!requests.unique()){//采用写时拷贝
                requests.reset(new set<weak_ptr<Request> >(*requests));
                cout<<"remove() copy on write"<<endl;
            }
            assert(requests.unique());
            requests->erase(one);//移除
            pthread_mutex_unlock(&mutex);
        }
        void printALL();
        Inventory(){
            pthread_mutex_init(&mutex,NULL);
            requests.reset(new set<weak_ptr<Request> >);
        }
    private:
        pthread_mutex_t mutex;
        shared_ptr<set<weak_ptr<Request> > > requests;//采用shared_ptr管理set容器,set元素是管理Request的weak_ptr
};
Inventory g_inventory;
class Request:public enable_shared_from_this<Request>{
    public:
        void process(){
            pthread_mutex_lock(&mutex);
            g_inventory.add(shared_from_this());//shared_from_this()将this指针转换为shared_ptr指针
            pthread_mutex_unlock(&mutex);
        }
        void cancel(){//Request对象必须显示调用此函数才能析构
            pthread_mutex_lock(&mutex);
            g_inventory.remove(shared_from_this());
            pthread_mutex_unlock(&mutex);
        }
        ~Request() __attribute__ ((noinline)){//告诉编译器此函数非内联
            cout<<"~Request()"<<endl;
        }
        void print(){// __attribute__ ((noninline)){
            pthread_mutex_lock(&mutex);
            cout<<"Request print()"<<endl;
            pthread_mutex_unlock(&mutex);
        }
    private:
        pthread_mutex_t mutex;
};
void Inventory::printALL(){//打印Request对象...读者
    shared_ptr<set<weak_ptr<Request> > > temp;
    {
        pthread_mutex_lock(&mutex);
        temp=requests;//读者采用局部变量temp保存requests副本,然后从temp中读取Request对象
        pthread_mutex_unlock(&mutex);
    }
    shared_ptr<Request> one;
    for(set<weak_ptr<Request> >::iterator it=(*temp).begin();it!=(*temp).end();it++){
        one=it->lock();
        if(one){
            one->print();
            cout<<"lock success"<<endl;
        }
        else{//这一步没什么意义了...采用weak_ptr<Request>作为set容器元素是由于在前面一个程序基础上改的,可以用shared_ptr<Request>作为set元素,不过weak_ptr<Request>有个好处是如果忘记了显式调用Request::cancel(),那么容器也就存放些weak_ptr,不至于使Request对象滞留内存,导致内存泄露....
            temp->erase(it);
            //it--;
            cout<<"lock failure"<<endl;
        }
    }
    cout<<"Inventory::printALL() unlocked"<<endl;
}
void* threadFun(void* arg){
    shared_ptr<Request> one(new Request);
    one->process();
    sleep(1);//###1###
    one->cancel();//Request对象析构前需显示调用此函数进行解注册
}
int main(){
    pthread_t pid;
    if(pthread_create(&pid,NULL,threadFun,NULL)<0)
        cout<<"pthread_create error"<<endl;
    usleep(500*1000);
    g_inventory.printALL();
    pthread_join(pid,NULL);
    return 0;
}

###1###不注释掉输出://此时读者还能读到为析构的Request对象

Request print()
lock success
Inventory::printALL() unlocked
~Request()


###1###注释掉程序输出://当读者printALL时Request对象已经析构了,set容器为空

~Request()
Inventory::printALL() unlocked

       

你可能感兴趣的:(临界区重叠出现的死锁)