上篇文章我们讲了几种智能指针原理以及实现方法,这篇文章我们主要讲一下shared_ptr的线程安全问题和循环引用问题。
目录
一.线程安全问题
shared_ptr的线程安全问题存在两方面
1.由于shared_ptr的原理是多个对象共用引用计数,那么在两个线程的智能指针同时进行++或者--操作时就会出现问题。假设引用计数的初始值是1,在两个线程同时++时就有可能使加完后的引用计数是2。这时候引用计数会发生错乱,会导致资源未释放或者程序奔溃的情况。所以引用计数的++或者--必须加锁,这样才能保证线程安全。(可以联系线程安全的同步与互斥)
2.如果智能指针管理的对象放在堆上,两个线程中同时访问,会导致线程出现问题(临界资源争抢问题)
解决方法:在需要加减引用计数的地方加锁
代码展示:
template<class T>
class Shared_Ptr
{
public:
Shared_Ptr(T* ptr = nullptr)
:_ptr(ptr)
, pCount(new int(1))
, pmutex(new mutex)
{
if (_ptr == nullptr)
*pCount = 1;
}
~Shared_Ptr()
{
//加锁
pmutex->lock();
--(*pCount);
//解锁
pmutex->unlock();
if (_ptr && pCount == 0)
{
delete _ptr;
delete pCount;
delete pmutex;
}
}
Shared_Ptr(Shared_Ptr<T>& cp)
{
_ptr = cp._ptr;
pCount = cp.pCount;
pmutex = cp.pmutex;
if (_ptr)
{
pmutex->lock();
++(*pCount);
}
pmutex->unlock();
}
Shared_Ptr<T>& operator=(Shared_Ptr<T> ap)
{
if (_ptr != ap._ptr)
{
//释放旧资源
if (_ptr && pCount == 0)
{
delete _ptr;
delete pCount;
delete pmutex;
}
_ptr = ap._ptr;
pCount = ap.pCount;
pmutex = ap.pmutex;
if (_ptr)
{
//加锁
pmutex->lock();
++(*pCount);
}
//解锁
pmutex->unlock();
}
return *this;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
int UseCount()
{
return *pCount;
}
private:
T* _ptr;
int* pCount;
mutex* pmutex;
};
二.循环引用
1.当两个智能指针指向两个节点(node1,node2),他们各自的引用计数+1,但是当node1的next指向node2,node2的prev指向node1,只是时候他们各自的引用计数会变为2。就是这种情况下在析构的时候会产生循环引用的情况。
- node1和node2在析构后,引用计数变为1,但是他们各自的next,prev还指向下一个节点
- 要想释放调node1就必须析构掉node2,因为node2的prev还指向node1,要想释放掉node2就必须析构掉node1,因为node1的next还指向node2
- 就是这时候会产生循环引用
注意:这里的循环引用和拷贝构造中不添加引用的话也会产生循环引用
图解
node1和node2对象析构使引用计数减1,是因为出了函数作用域会自动调用析构函数使引用计数减一。这时候引用计数变为1。而要想释放这两个节点就会产生循环引用
struct ListNode
{
shared_ptr<ListNode> prev;
shared_ptr<ListNode> next;
int data;
~ListNode()
{
cout << "~ListNode()" << endl;
}
};
int main()
{
shared_ptr<ListNode> node1(new ListNode);
shared_ptr<ListNode> node2(new ListNode);
cout << node1.use_count() << endl;
cout << node2.use_count() << endl;
node1->next = node2;
cout << node1.use_count() << endl;
cout << node2.use_count() << endl;
node2->prev = node1;
cout << node1.use_count() << endl;
cout << node2.use_count() << endl;
system("pause");
return 0;
}
2.解决方法
- 在引用计数的 场景下,把节点中的prev,next改为weak_ptr
- 原理:在一个节点指向另一个节点的时候不会使引用计数+1
注意: 虽然通过弱引用指针可以有效的解除循环引用, 但这种方式必须在程序员能预见会出现循环引用的情况下才能使用, 也可以是说这个仅仅是一种编译期的解决方案, 如果程序在运行过程中出现了循环引用, 还是会造成内存泄漏.
struct ListNode
{
weak_ptr<ListNode> prev;
weak_ptr<ListNode> next;
int data;
~ListNode()
{
cout << "~ListNode()" << endl;
}
};
int main()
{
shared_ptr<ListNode> node1(new ListNode);
shared_ptr<ListNode> node2(new ListNode);
cout << node1.use_count() << endl;
cout << node2.use_count() << endl;
node1->next = node2;
cout << node1.use_count() << endl;
cout << node2.use_count() << endl;
node2->prev = node1;
cout << node1.use_count() << endl;
cout << node2.use_count() << endl;
system("pause");
return 0;
}