菜鸟笔记
提升您的技术认知

智能指针解决循环引用和线程安全问题

std::shared_ptr的线程安全问题

通过这个程序我们来测试shared_ptr的线程安全问题,需要注意shared_ptr的线程安全分为两方面:

// 1.演示引用计数线程安全问题,就把AddRefCount和SubRefCount中的锁去掉
// 2.演示可能不出现线程安全问题,因为线程安全问题是偶现性问题,main函数的n改大一些概率就变大了,就容易出现了。
// 3.下面代码我们使用SharedPtr演示,是为了方便演示引用计数的线程安全问题,将代码中的SharedPtr换成sshared_ptr进行测试,可以验证库的shared_ptr,发现结论是一样的。
void SharePtrFunc(SharedPtr<Date>& sp, size_t n) {
	cout << sp.Get() << endl;
	for (size_t i = 0; i < n; ++i)
	{
		// 这里智能指针拷贝会++计数,智能指针析构会--计数,这里是线程安全的。
		SharedPtr<Date> copy(sp);
		// 这里智能指针访问管理的资源,不是线程安全的。所以我们看看这些值两个线程++了2n次,但是最
		终看到的结果,并一定是加了2n
			copy->_year++;
		copy->_month++;
		copy->_day++;
	}
}
int main()
{
	SharedPtr<Date> p(new Date);
	cout << p.Get() << endl;
	const size_t n = 100;
	thread t1(SharePtrFunc, p, n);
	thread t2(SharePtrFunc, p, n);
	t1.join();
	t2.join();
	cout << p->_year << endl;
	cout << p->_month << endl;
	cout << p->_day << endl;
	return 0;
}

1.智能指针对象中引用计数是多个智能指针对象共享的,两个线程中智能指针的引用计数同时++或者--,这个操作不是原子的,引用计数原来是1,++了两次,可能还是2,这样引用计数就乱了,有可能造成资源未释放或者程序崩溃的风险。所以说智能指针中++或--的操作是需要加锁的,也就是说引用计数的操作是线程安全的

2.智能指针的对象存放在堆上,两个线程同时去访问,就会造成线程安全问题

 

std::shared_ptr循环引用

struct ListNode
{
	int _data;
	shared_ptr<ListNode> _prev;
	shared_ptr<ListNode> _next;
	~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;
	node2->_prev = node1;
	cout << node1.use_count() << endl;
	cout << node2.use_count() << endl;
	return 0;
}
  1.  node1和node2两个智能指针对象指向两个节点,引用计数变为1,我们不需要手动delete
  2. node1的_next指向node2,node2的_prev指向node1,引用计数变成2
  3. node1和node2析构,引用计数减到1,但是_next还指向下一个节点,_prev指向上一个节点
  4. 也就是说_next析构了,node2释放了
  5. 也就是说_prev析构了,node1释放了
  6. 但是_next属于node的成员,node1释放了,_next才会析构,而node1由_prev管理,_prev属于node2成员,所以这就叫循环引用,谁都不会释放

解决方案

在引用计数的场景下,把shared_ptr换成weak_ptr就可以了

原理就是,node1->_next = node2; 和 node2->_prev = node1; 时weak_ptr的_next和_prev不会增加node1和node2的引用计数

struct ListNode
{
int _data;
weak_ptr<ListNode> _prev;
weak_ptr<ListNode> _next;
~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;
node2->_prev = node1;
cout << node1.use_count() << endl;
cout << node2.use_count() << endl;
return 0; }

 

如果不是new出来的空间如何用智能指针管理呢?

其实shared_ptr设计了一个删除器来解决这个问题

// 仿函数的删除器
template<class T>
struct FreeFunc {
	void operator()(T* ptr)
	{
		cout << "free:" << ptr << endl;
		free(ptr);
	}
};
template<class T>
struct DeleteArrayFunc {
	void operator()(T* ptr)
	{
		cout << "delete[]" << ptr << endl;
		delete[] ptr;
	}
};
int main()
{
	FreeFunc<int> freeFunc;
	shared_ptr<int> sp1((int*)malloc(4), freeFunc);
	DeleteArrayFunc<int> deleteArrayFunc;
	shared_ptr<int> sp2((int*)malloc(4), deleteArrayFunc);
	return 0;
}