智能指针是线程安全的?
智能指针实现原理?
智能指针(Smart Pointers)是一种用于管理动态分配的内存资源的 C++ 类模板,不是指某个关键字,确保在不再需要时自动释放所持有的资源,从而避免内存泄漏和悬空指针等内存管理问题; 这个类里面通过引用计数 来跟踪指向同一对象的指针数量, 每当一个智能指针指向对象时,对象的引用计数会增加;当智能指针超出作用域或被销毁时, 引用计数会减少。当 引用计数为零 时,表示没有指针指向对象,可以安全地释放对象的内存。
std::shared_ptr:共享式智能指针,允许多个指针共享同一个资源,这个资源可以被其他shared\_ptr 引用,所以需要使用引用计数来管理资源的生命周期。std::unique_ptr:独占式智能指针,不能被复制,只能移动所有权,由于unique\_ptr是独占所指向对象的所有权,不能共享,所以不使用引用计数,使用独占性质来管理对象的生命周期。std::weak_ptr:弱引用智能指针,可以解决std::shared_ptr的循环引用问题,不会增加资源的引用计数,不会延长资源的生命周期。
智能指针是线程安全吗?
先说下什么是线程安全? 先说下什么是线程安全?
线程安全指某个函数、函数库在多线程环境中被调用时,能够正确地处理多个线程之间的公用变量,使程序功能正确完成;
举一个线程操作不安全的例子:
10个线程,对global\_counter 自加,期望值是 200000 ;
我们都知道类似 i++的操作,底层涉及了 读取、修改和写回多个步骤,所以这 ++操作 并不是线程安全的;
#include <stdio.h>
#include <pthread.h>
#define NUM_THREADS 10
#define ITERATIONS 10000
int global_counter = 0;
void *thread_function(void *arg) {
for (int i = 0; i < ITERATIONS; ++i) {
global_counter++;
}
return NULL;
}
int main() {
pthread_t threads[NUM_THREADS];
// 创建多个线程
for (int i = 0; i < NUM_THREADS; ++i) {
pthread_create(&threads[i], NULL, thread_function, NULL);
}
// 等待所有线程完成
for (int i = 0; i < NUM_THREADS; ++i) {
pthread_join(threads[i], NULL);
}
printf("Global counter value: %d\n", global_counter);
return 0;
}
结果为 217250,和预期的 200000 不一致
root@ubuntu:/home# gcc thread.c -o thread -pthread
root@ubuntu:/home# ./thread
Global counter value: 217250
root@ubuntu:/home#
智能指针类的里面的关键操作
从智能指针的作用来看,只是管理内存的生命周期,并不负责竞争和同步问题
从智能指针内部类实现来看,最为关键是 引用计数 的自增,多线程场景,使用shared\_ptr,这个时候是有可能? 为了确定下,直接用gdb 进去看下(我猜测时在 拷贝构造的时候会调用计数+1操作)
#include <iostream>
#include <memory>
#include <vector>
int main() {
// 创建一个 std::shared_ptr,并将其初始化为指向一个动态分配的整数数组
std::shared_ptr<std::vector<int>> ptr1 = std::make_shared<std::vector<int>>(5, 10); // 创建一个包含5个元素的整数数组
// 创建另一个 std::shared_ptr,指向相同的对象
std::shared_ptr<std::vector<int>> ptr2 = ptr1;
// 打印每个 shared_ptr 的引用计数
std::cout << "ptr1 引用计数: " << ptr1.use_count() << std::endl;
std::cout << "ptr2 引用计数: " << ptr2.use_count() << std::endl;
// 创建第三个 shared_ptr,指向相同的对象 //断点打在行!
std::shared_ptr<std::vector<int>> ptr3 = ptr1;
// 打印每个 shared_ptr 的引用计数
std::cout << "ptr1 引用计数: " << ptr1.use_count() << std::endl;
std::cout << "ptr2 引用计数: " << ptr2.use_count() << std::endl;
std::cout << "ptr3 引用计数: " << ptr3.use_count() << std::endl;
return 0;
}
17行打两个断点,
(gdb)
main () at shareptr.cc:13
13 std::cout << "ptr1 引用计数: " << ptr1.use_count() << std::endl;
(gdb)
ptr1 引用计数: 2
14 std::cout << "ptr2 引用计数: " << ptr2.use_count() << std::endl;
(gdb)
ptr2 引用计数: 2
# step 进入
17 std::shared_ptr<std::vector<int>> ptr3 = ptr1;
(gdb) s
std::shared_ptr<std::vector<int, std::allocator<int> > >::shared_ptr (
this=0x7fffffffe490) at /usr/include/c++/7/bits/shared_ptr.h:119
119 shared_ptr(const shared_ptr&) noexcept = default;
#拷贝构造函数的默认声明,使用默认实现,
# step 再进入
(gdb) s
std::__shared_ptr<std::vector<int, std::allocator<int> >, (__gnu_cxx::_Lock_policy)2>::__shared_ptr (this=0x7fffffffe490)
at /usr/include/c++/7/bits/shared_ptr_base.h:1121
1121 __shared_ptr(const __shared_ptr&) noexcept = default;
# step 再进入,终于可能到实现函数了
(gdb) s
std::__shared_count<(__gnu_cxx::_Lock_policy)2>::__shared_count (
this=0x7fffffffe498, __r=...)
at /usr/include/c++/7/bits/shared_ptr_base.h:688
688 : _M_pi(__r._M_pi)
#next 逐步看
(gdb) n
690 if (_M_pi != 0)
# 打印发现有用的信息, _M_use_count = 2 ,符合当前的 引用计数
(gdb) p *_M_pi
$2 = {<std::_Mutex_base<(__gnu_cxx::_Lock_policy)2>> = {<No data fields>},
_vptr._Sp_counted_base = 0x555555758c80 <vtable for std::_Sp_counted_ptr_inplace<std::vector<int, std::allocator<int> >, std::allocator<std::vector<int, std::allocator<int> > >, (__gnu_cxx::_Lock_policy)2>+16>,
_M_use_count = 2, _M_weak_count = 1}
#next 逐步看,发现add
(gdb) n
691 _M_pi->_M_add_ref_copy();
# step 继续深入,发现时 c++ 的atomic操作, 这个就是原子操作
(gdb) s
std::_Sp_counted_base<(__gnu_cxx::_Lock_policy)2>::_M_add_ref_copy (
this=0x55555576be70) at /usr/include/c++/7/bits/shared_ptr_base.h:138
138 { __gnu_cxx::__atomic_add_dispatch(&_M_use_count, 1); }
从代码上看,引用计数累计时原子操作,所以说明 shared\_ptr 内部操作是线程安全的,但是不能保证多线程场景下用 shared\_ptr 管理内存数据的就一定是线程安全性