基于Boost库, C++11 加入了shared_ptr和weak_ptr. 它们最早在TR1中就被引入, 但在C++11中, 在Boost的基础上又加入了新的功能.
std::shared_ptr使用引用计数. 每一个shared_ptr的拷贝都指向相同的内存. 在最后一个shared_ptr析构的时候, 内存才会被释放.
std::shared_ptr<int> p1(new int(5));
std::shared_ptr<int> p2 = p1; // 都指向同一内存.
p1.reset(); // 因为p2还在,所以内存没有释放.
p2.reset(); // 释放内存, 因为没有shared_ptr指向那块内存了.
std::shared_ptr 使用引用计数, 所以有循环计数的问题. 为了打破循环,可以使用std::weak_ptr. 顾名思义, weak_ptr是一个弱引用, 只引用, 不计数. 如果一块内存被shared_ptr和weak_ptr同时引用, 当所有shared_ptr析构了之后,不管还有没有weak_ptr引用该内存, 内存也会被释放. 所以weak_ptr不保证它指向的内存一定是有效的, 在使用之前需要检查.
std::shared_ptr<int> p1(new int(5));
std::weak_ptr<int> wp1 = p1; // 还是只有p1有所有权.
{
std::shared_ptr<int> p2 = wp1.lock(); // p1和p2都有所有权
if(p2) // 使用前需要检查
{
// 使用p2
}
} // p2析构了, 现在只有p1有所有权.
p1.reset(); // 内存被释放.
std::shared_ptr<int> p3 = wp1.lock(); // 因为内存已经被释放了, 所以得到的是空指针.
if(p3)
{
// 不会执行到这.
}
shared_ptr的成员方法,user_count()的作用是获得当前对象被引用的次数,reset()的作用是释放指针对对象的引用,将指针设为空。
shared_ptr的线程安全性:
它是这样说的:
“Boost 文档对于 shared_ptr 的线程安全有一段专门的记述,内容如下:
shared_ptr objects offer the same level of thread safety as built-in types. A shared_ptr instance can be "read" (accessed using only const operations) simultaneously by multiple threads. Different shared_ptr instances can be "written to" (accessed using mutable operations such as operator= or reset) simultaneosly by multiple threads (even when these instances are copies, and share the same reference count underneath.)
Any other simultaneous accesses result in undefined behavior.
翻译为中文如下:
shared_ptr 对象提供与内建类型一样的线程安全级别。一个 shared_ptr 实例可以同时被多个线程“读”(仅使用不变操作进行访问)。 不同的 shared_ptr 实例可以同时被多个线程“写入”(使用类似 operator= 或 reset 这样的可变操作进行访问)(即使这些实 例是拷贝,而且共享下层的引用计数)。
任何其它的同时访问的结果会导致未定义行为。”
这几句话比较繁琐,我总结一下它的意思:
1 同一个shared_ptr被多个线程“读”是安全的。
2 同一个shared_ptr被多个线程“写”是不安全的。
3 共享引用计数的不同的shared_ptr被多个线程”写“ 是安全的。
如何印证上面的观点呢?
其实第一点我觉得比较多余。因为在多个线程中读同一个对象,在正常情况下不会有什么问题。
所以问题就是:如何写程序证明同一个shared_ptr被多个线程"写"是不安全的?
我的思路是,在多个线程中同时对一个shared_ptr循环执行两遍swap。 shared_ptr的swap函数的作用就是和另外一个shared_ptr交换引用对象和引用计数,是写操作。执行两遍swap之后, shared_ptr引用的对象的值应该不变。
程序如下:
#include <stdio.h>
#include <tr1/memory>
#include <pthread.h>
using std::tr1::shared_ptr;
shared_ptr<int> gp(new int(2000));
shared_ptr<int> CostaSwapSharedPtr1(shared_ptr<int> & p)
{
shared_ptr<int> p1(p);
shared_ptr<int> p2(new int(1000));
p1.swap(p2);
p2.swap(p1);
return p1;
}
shared_ptr<int> CostaSwapSharedPtr2(shared_ptr<int> & p)
{
shared_ptr<int> p2(new int(1000));
p.swap(p2);
p2.swap(p);
return p;
}
void* thread_start(void * arg)
{
int i =0;
for(;i<100000;i++)
{
shared_ptr<int> p= CostaSwapSharedPtr2(gp);
if(*p!=2000)
{
printf("Thread error. *gp=%d \n", *gp);
break;
}
}
printf("Thread quit \n");
return 0;
}
int main()
{
pthread_t thread;
int thread_num = 10, i=0;
pthread_t* threads = new pthread_t[thread_num];
for(;i<thread_num;i++)
pthread_create(&threads[i], 0 , thread_start , &i);
for(i=0;i<thread_num;i++)
pthread_join(threads[i],0);
delete[] threads;
return 0;
}
这个程序中我启了10个线程。每个线程调用10万次 CostaSwapSharedPtr2函数。 在CostaSwapSharePtr2函数中,对同一个share_ptr全局变量gp进行两次swap(写操作), 在函数返回之后检查gp的值是否被修改。如果gp值被修改,则证明多线程对同一个share_ptr执行写操作是不安全的。
程序运行的结果如下:
Thread error. *gp=1000
Thread error. *gp=1000
Thread quit
Thread quit
Thread error. *gp=1000
Thread quit
Thread error. *gp=1000
Thread quit
Thread error. *gp=1000
Thread quit
Thread error. *gp=1000
Thread quit
Thread error. *gp=1000
Thread quit
Thread error. *gp=1000
Thread quit
Thread error. *gp=1000
Thread quit
Thread quit
10个线程有9个出错。证明多线程对同一个share_ptr执行写操作是不安全的。
我们在程序中,如果不运行CostaSwapSharedPtr2, 改成运行CostaSwapSharedPtr1呢?
CostaSwapSharedPtr1和CostaSwapSharedPtr2的区别在于, 它不是直接对全局变量gp进行写操作,而是将gp拷贝出来一份再进行写操作。运行的结果如下:
cherry@ubuntu:~/test/cpp/shared_ptr$ ./b
Thread quit
Thread quit
Thread quit
Thread quit
Thread quit
Thread quit
Thread quit
Thread quit
Thread quit
Thread quit