C++智能指针的多线程访问共享对象问题以及自定义删除器

  • Post author:
  • Post category:其他


多线程访问共享对象问题

问题描述:线程A和线程B访问一个共享的对象,如果线程A正在析构这个对象的时候,线程B又要调用该共享对象的成员方法,此时可能线程A已经把对象析构完了,线程B再去访问该对象,就会发生不可预期的错误。

#include <iostream>
#include <thread>
using namespace std;

class Test
{
public:
	// 构造Test对象,_ptr指向一块int堆内存,初始值是20
	Test() :_ptr(new int(20)) 
	{
		cout << "Test()" << endl;
	}
	// 析构Test对象,释放_ptr指向的堆内存
	~Test()
	{
		delete _ptr;
		_ptr = nullptr;
		cout << "~Test()" << endl;
	}
	// 该show会在另外一个线程中被执行
	void show()
	{
		cout << *_ptr << endl;
	}
private:
	int *volatile _ptr;
};
void threadProc(Test *p)
{
	// 睡眠两秒,此时main主线程已经把Test对象给delete析构掉了
	std::this_thread::sleep_for(std::chrono::seconds(2));
	/* 
	此时当前线程访问了main线程已经析构的共享对象,结果未知,隐含bug。
	此时通过p指针想访问Test对象,需要判断Test对象是否存活,如果Test对象
	存活,调用show方法没有问题;如果Test对象已经析构,调用show有问题!
	*/
	p->show();
}
int main()
{
	// 在堆上定义共享对象
	Test *p = new Test();
	// 使用C++11的线程类,开启一个新线程,并传入共享对象的地址p
	std::thread t1(threadProc, p);
	// 在main线程中析构Test共享对象
	delete p;
	// 等待子线程运行结束
	t1.join();
	return 0;
}

运行上面的代码,发现在main主线程已经delete析构Test对象以后,子线程threadProc再去访问Test对象的show方法,无法打印出*_ptr的值20。

shared_ptr和weak_ptr是线程安全的智能指针,

可以用shared_ptr和weak_ptr来解决多线程访问共享对象的线程安全问题,上面代码修改如下:

class Test
{
public:
	Test(const char *ptr) :mptr(new char[strlen(ptr) + 1])
	{
		cout << "Test()" << endl;
		strcpy(mptr, ptr);
	}
	~Test()
	{
		cout << "~Test()" << endl;
		delete[]mptr;
		mptr = nullptr;
	}
	void show() { cout << mptr << endl; }
private:
	char *mptr;
};
// 子线程   p
void thread_proc(weak_ptr<Test> wp, const char *threadname)
{
	cout << threadname << endl;
	// 睡眠2s
	std::this_thread::sleep_for(std::chrono::seconds(2));
	cout << "子线程2s睡眠已到!" << endl;

	//p->show();
	/*
	这个线程在提升的过程中,刚好其它线程在释放
	*/
	shared_ptr<Test> sp = wp.lock();  // 2-1 = 1
	if (sp != nullptr)
	{
		sp->show();
	}
	else
	{
		cout << "Test对象已经析构,不能继续访问!" << endl;
	}
}

void func()
{
	//Test *p = new Test("hello world");
	shared_ptr<Test> p(new Test("hello world"));
	// 创建线程对象,就自动开启线程了
	/*
	thread(xx, shared_ptr<Test> p)
	*/
	thread t1(thread_proc, weak_ptr<Test>(p), "child thread");
	// pthread_create

	t1.join();
	//t1.detach(); // 设置子线程为分离线程

	//delete p;
	// t1.join   pthread_join
	// t1.detach pthread_detach  设置分离线程
	cout << "main thread end!" << endl;
} //1 - 1 = 0
int main()
{
	cout << "main thread begin!" << endl;
	func();
	// 主线程等待20秒,查看子线程的运行情况
	this_thread::sleep_for(chrono::seconds(20));

	return 0;
}

如果设置t1为分离线程,让main主线程结束,p智能指针析构,进而把Test对象析构,此时show方法已经不会被调用,

因为在threadProc方法中,pw提升到ps时,lock方法判定Test对象已经析构,提升失败

!main函数代码可以如下修改测试:

int main()
{
	// 在堆上定义共享对象
	shared_ptr<Test> p(new Test);
	// 使用C++11的线程,开启一个新线程,并传入共享对象的弱智能指针
	std::thread t1(threadProc, weak_ptr<Test>(p));
	// 在main线程中析构Test共享对象
	// 设置子线程分离
	t1.detach();
	return 0;
}

该main函数运行后,最终的threadProc中,show方法不会被执行到。

以上是在多线程中访问共享对象时,对shared_ptr和weak_ptr的一个典型应用

自定义删除器


我们经常用智能指针管理的资源是堆内存,当智能指针出作用域的时候,在其析构函数中会delete释放堆内存资源,但是



除了堆内存资源,智能指针还可以管理其它资源




比如打开的文件,此时对于文件指针的关闭,就不能用delete了,这时我们需要


自定义智能指针释放资源的方式




unique_ptr智能指针的析构函数源码如下:

~unique_ptr() noexcept
{    // destroy the object
    if (get() != pointer())
    {
        this->get_deleter()(get()); // 这里获取底层的删除器,进行函数对象的调用
    }
}



从unique_ptr的析构函数可以看到,如果要实现一个自定义的删除器,实际上就是定义一个函数对象而已,示例代码如下:

class FileDeleter
{
public:
    // 删除器负责删除资源的函数
    void operator()(FILE *pf)
    {
        fclose(pf);
        pf = nullptr;
    }
};
int main()
{
    // 由于用智能指针管理文件资源,因此传入自定义的删除器类型FileDeleter
    unique_ptr<FILE, FileDeleter> filePtr(fopen("data.txt", "w"));
    return 0;
}



以下是一个模拟源码写的一个带删除器的引用计数的智能指针

class RefCnt
{
public:
	// 给资源添加引用计数
	void add(void *ptr)
	{
		auto it = mrefCntMap.find(ptr);
		if (it != mrefCntMap.end())
		{
			it->second++;
		}
		else
		{
			// make_pair(ptr,1);
			mrefCntMap.insert({ ptr, 1 });
		}
	}
	// 给资源减少引用计数
	void del(void *ptr)
	{
		auto it = mrefCntMap.find(ptr);
		if (it != mrefCntMap.end())
		{
			if (--(it->second) == 0)
			{
				mrefCntMap.erase(it);
			}
		}
	}
	// 返回指定资源的引用计数
	int get(void *ptr)
	{
		auto it = mrefCntMap.find(ptr);
		if (it != mrefCntMap.end())
		{
			return it->second;
		}
		return -1;
	}
private:
	// 一个资源void* 《=》 计数器 int
	unordered_map<void*, int> mrefCntMap;
};
// 提供一个默认的删除器,默认删除的是堆内存资源
template<typename T>
class Deletor
{
public:
	void operator()(T *ptr) // 一元函数对象
	{
		delete ptr; // 默认删除的是堆内存资源
	}
};
// 自定义的智能指针 T资源的类型   D删除器的类型
template<typename T, typename D = Deletor<T>>
class CSmartPtr
{
public:
	// 构造函数
	CSmartPtr(T *ptr = nullptr, const D &d=D())
		:mptr(ptr), mdeletor(d)
	{
		if (mptr != nullptr)
		{
			mrefCnt.add(mptr);
		}
	}
	~CSmartPtr()
	{
		mrefCnt.del(mptr);
		if (0 == mrefCnt.get(mptr))
		{
			cout << "释放默认的堆内存资源" << endl;
			mdeletor(mptr); // 通过删除器来释放资源
		}
	}
	CSmartPtr(const CSmartPtr<T> &src)
		:mptr(src.mptr)
	{
		if (mptr != nullptr)
		{
			mrefCnt.add(mptr);
		}
	}
	CSmartPtr<T>& operator=(const CSmartPtr &src)
	{
		if (this == &src)
			return *this;

		mrefCnt.del(mptr);
		if (0 == mrefCnt.get(mptr))
			delete mptr;

		mptr = src.mptr;
		if (mptr != nullptr)
		{
			mrefCnt.add(mptr);
		}

		return *this;
	}


	// 指针常用运算符重载函数
	T& operator*() { return *mptr; }
	const T& operator*()const { return *mptr; }
	T* operator->() { return mptr; }

private:
	T *mptr;  // FILE *pf;    delete pf;   fclose(pf);
	D mdeletor;
	static RefCnt mrefCnt;
};
template<typename T, typename D>
RefCnt CSmartPtr<T, D>::mrefCnt;

int main()
{
	// 智能指针  管理资源 =》  堆内存 文件
	CSmartPtr<int> ptr2(new int);

	class FileDeletor
	{
	public:
		void operator()(FILE *pf) const
		{ 
			cout << "释放文件资源" << endl;
			fclose(pf); 
		}
	};
	CSmartPtr<FILE, FileDeletor> ptr(fopen("data.txt", "w+"));


	
	unique_ptr<int> ptr3(new int);

	unique_ptr<FILE, FileDeletor> ptr4(fopen("d", "w+"));
	unique_ptr<FILE> ptr5(fopen("dd", "w+")});

	return 0;
}



版权声明:本文为zhangfei5354原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。