多线程访问共享对象问题
问题描述:线程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;
}