临界区重叠出现的死锁
死锁例子:
一些函数看似其本身是线程安全的,但是当多个函数同时执行时出现了非线程安全。一个例子是多个函数在多线程下同时执行时临界区相互重叠
Requset向Inventory注册,然后Invetory存有Request对象的指针。当Request对象析构自身时向Inventory中移除指针,此时若析构停住,然后Inventory持有该Request指针做一些操作,将访问到Request这个半成品对象。且为了保证每个函数线程安全采用锁保护,最后多个函数同时执行导致死锁。
#include<iostream>#include<string>#include<unistd.h>#include<pthread.h>#include<set>#include<boost/shared_ptr.hpp>#include<boost/weak_ptr.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: //void process(){//###1###这里原本设计不需要参数a且和下面一句使用,这就出现了有两批shared_ptr对象在管理Request对象,最后在process退出时一批shared_ptr将Request对象析构,然后另一批对象即###2###处shared_ptr退出时再次析构shared_ptr造成double free...汗...还没有适应shared_ptr void process(shared_ptr<Request>& a){//###3### //shared_ptr<Request> a(this);//###1### pthread_mutex_lock(&mutex); g_inventory.add(a); 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()//###1### one->process(one);//###3###替换###1###处的解决方案 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;}Request print()
lock success
Inventory::printALL() unlocked
~Request()
###4###注释掉的输出:
~Request()
lock failure
注:这种方式存在轻微的内存泄露,当Request对象析构后,Inventory不会立即将相应的weak_ptr从容器中删除,而是到下一次遍历容器时才会删除(延迟删除)。
将print()移除printALL()的临界区,采用写时拷贝技术。一个简单的方法是:在Inventory::printALL()内用一个临时容器temp在临界区内复制requests_全部元素,然后temp在临界区外执行遍历操作。这样显然拷贝整个容器非上策。还有个方法是:采用shared_ptr管理set容器。明天再写...