线程相关
约 5401 字大约 18 分钟
2025-06-21
前要:本节必须需要有线程进程的多并发开发基础才可以进行学习。
1.thread 线程库
不同操作系统平台的线程接口都是不一样的,C++ STL 为了适应不同平台的线程开发,制作了线程库来适应各个平台。线程库中主要包含两个类,一个是 class thread{/* ... */};,另外一个就是 class this_thread{/* ... */};。我们可以直接来使用一下:
//创建多线程并且使用
#include <iostream>
#include <vector>
#include <thread>
#include <windows.h>
using namespace std;
//多线程任务
void Print(int n)
{
Sleep(3000);
for (int i = 0; i < n; ++i)
{
cout << this_thread::get_id() << " 结果:" << i << '\n'; //使用全局的金泰函数在线程内部获取进程 tid
this_thread::sleep_for(chrono::milliseconds(100)); //休眠 100 微秒这个时间段后,再进行输出
//还有另外一个静态方法就是 sleep_until() 就是睡眠到某一个时间点
}
}
int main()
{
//创建多个线程
vector<thread> threads;
for (int i = 0; i < 3; i++)
{
thread t = thread(Print, 10);
cout << t.get_id() << endl; //注意这个函数不能在 move 后调用,因为 move 后资源被掠夺
threads.push_back(move(t)); //注意这里只能使用移动构造(线程对象不允许拷贝)
}
//释放多个线程
for (int i = 0; i < 3; i++)
{
threads[i].join();
}
return 0;
}如果遗漏 join() 就会报错,安全性会更好。
在 class this_thread{} 中有一个方法很有趣,就是 yield(),yield 就是“放弃”的意思,该方法允许当前线程主动放弃 CPU 的使用权,让其他线程或进程有机会运行。
- 提高并发性能:在某些情况下,一个线程可能会占用太多
CPU资源,导致其他线程无法得到执行的机会。通过在适当的时候调用yield()函数,可以平衡各个线程之间的执行,提高整体的并发性能和响应性。 - 避免忙等待:有时候,某个线程处于忙等待状态,持续地检查某个条件或资源是否满足。在这种情况下,可以在每次检查失败时调用
yield()函数,让出CPU,减少资源的消耗,避免造成不必要的CPU负载。 - 协作多任务:在一些多任务协作的场景中,
yield()函数可以用于控制任务的执行顺序,确保各个任务之间的平衡和公平
为什么线程不可以拷贝呢?很正常,资源所有权的问题。线程是操作系统级别的资源,如果允许拷贝线程对象,那么复制后的线程对象可能与原始线程对象共享相同的资源,这会导致难以预测的行为和资源竞争。
分离线程 detach(),将线程与线程对象分离,使得线程独立运行,线程结束后会自动释放资源。
joinable() 检查线程对象是否已经被 join 或 detach,返回 bool 类型(防止重复 join() 和 detach())。
hardware_concurrency() 静态成员函数,返回当前硬件支持的并发线程数目,用来更好适应不同平台的并发编程。
swap() 可以交换两个线程对象的内容。
补充:更详细的说明和描述您可以去 cplusplus-thread 上查询。
2.mutex 互斥锁库
同样,为了可移植性,也需要有互斥锁库,解决数据二义性的问题。如果允许拷贝一个已经加锁的互斥锁,那么拷贝的锁对象是否应该继续持有原始锁对象的锁状态?这样的设计会导致编程模型变得复杂且难以理解。每个锁对象应该只由一个线程拥有,并且只对应一个共享资源的访问控制。
2.1.mutex
//多线程内使用全局锁
#include <iostream>
#include <mutex>
#include <thread>
std::mutex mtx;
int main()
{
int x = 0;
std::thread t1([&x]() {
for (int i = 0; i < 10; i++)
{
mtx.lock();
x++;
std::cout << x << std::endl;
mtx.unlock();
}
}
);
std::thread t2([&x]() {
for (int i = 0; i < 10; i++)
{
mtx.lock();
x++;
std::cout << x << std::endl;
mtx.unlock();
}
}
);
t1.join();
t2.join();
return 0;
}//多线程内使用局部锁(使用引用)
#include <iostream>
#include <thread>
#include <mutex>
void Func(int& number, std::mutex& mtx) //相当于这里的引用不是引用在创建 thread 的函数参数,而是函数参数的拷贝的引用
{
for (int i = 0; i < 10; i++)
{
mtx.lock();
std::cout << number++ << std::endl;
mtx.unlock();
}
}
int main()
{
int number = 1;
std::mutex mtx;
std::thread t(Func, std::ref(number), std::ref(mtx)); //由于 Func 的参数传递过程中,经转了很多步骤才给 Func,中间发送了一些左右值属性丢失,要想传引用必须加上 ref()。当然,如果是传递 const& 则可以忽略这一操作。ref() 能保证一个参数在传递过程中保持引用,比 & 更强力
for (int i = 0; i < 10; i++)
{
mtx.lock();
std::cout << number++ << std::endl;
mtx.unlock();
}
t.join();
return 0;
}//多线程内使用局部锁(使用指针)
#include <iostream>
#include <thread>
#include <mutex>
void Func(int& number, std::mutex* mtx) //相当于这里的引用不是引用在创建 thread 的函数参数,而是函数参数的拷贝的引用
{
for (int i = 0; i < 10; i++)
{
mtx->lock();
std::cout << number++ << std::endl;
mtx->unlock();
}
}
int main()
{
int number = 1;
std::mutex mtx;
std::thread t(Func, std::ref(number), &mtx); //由于 Func 的参数传递过程中,经转了很多步骤才给 Func,中间发送了一些左右值属性丢失,要想传引用必须加上 ref()
for (int i = 0; i < 10; i++)
{
mtx.lock();
std::cout << number++ << std::endl;
mtx.unlock();
}
t.join();
return 0;
}//多线程内使用局部锁(lambda)
#include <iostream>
#include <thread>
#include <mutex>
int main()
{
int number = 1;
std::mutex mtx;
std::thread t([&mtx](int& number) {
for (int i = 0; i < 10; i++)
{
mtx.lock();
std::cout << number++ << std::endl;
mtx.unlock();
}
},
std::ref(number)
); //由于 Func 的参数传递过程中,经转了很多步骤才给 Func,中间发送了一些左右值属性丢失,要想传引用必须加上 ref()
for (int i = 0; i < 10; i++)
{
mtx.lock();
std::cout << number++ << std::endl;
mtx.unlock();
}
t.join();
return 0;
}此外,还有一个 try_lock(),该方法会尝试申请锁,一旦申请失败就会返回错误,而不会进入阻塞,这比普通的锁更加灵活。
而而除了上述用到的普通锁,还有一些其他类型的锁,例如:递归互斥锁(recursive_mutex)、时间互斥锁(timed_mutex)、智能互斥锁(lock_guard)、唯一互斥锁(unique_lock)。
2.2.recursive_mutex
有一种常见加普通的锁会有问题,也就是递归函数,一旦调用就立刻死锁,并且无法解开。而递归互斥锁实际上就是判断申请锁的对象是否为本线程然后解决死锁的问题。
//使用递归互斥锁
#include <iostream>
#include <thread>
#include <mutex>
std::recursive_mutex rmtx; //std::mutex mtx; //普通锁
void recursiveFunction(int count)
{
if (count <= 0)
{
return;
}
rmtx.lock(); //mtx.lock(); //第一次加锁
std::cout << "Locked by thread: " << std::this_thread::get_id() << std::endl;
recursiveFunction(count - 1); //递归调用
rmtx.unlock(); //mtx.unlock(); //第一次解锁
std::cout << "Unlocked by thread: " << std::this_thread::get_id() << std::endl;
}
int main()
{
std::thread t(recursiveFunction, 3);
t.join();
return 0;
}2.3.timed_mutex
主要是在普通锁的基础上加上 try_lock_for() 和 try_lock_until(),功能实际上就是加锁一段时间或在某个时间点加锁(这里可能会使用一个叫 chrono 的命名空间,您可以前去查询一下)。
2.4.lock_guard
抛出异常时可能导致死锁问题。
//短暂捕获避免因异常出现死锁
#include <iostream>
#include <vector>
#include <thread>
#include <mutex>
using namespace std;
int main()
{
int n = 5;
vector<thread> threads(5);
mutex mtx;
for (int i = 0; i < 5; ++i)
{
threads[i] = thread([&]() {
for (int i = 0; i < n; ++i)
{
mtx.lock();
try {
//一旦这里出现异常,很有可能没有释放锁就抛出
cout << this_thread::get_id() << ":" << i << endl;
this_thread::sleep_for(chrono::milliseconds(100));
}
catch (...) { //短暂捕捉
mtx.unlock();
throw; //重新抛出
}
mtx.unlock();
}
}
);
}
for (auto& e : threads)
{
e.join();
}
return 0;
}//自定义智能锁避免因异常出现死锁
#include <iostream>
#include <vector>
#include <thread>
#include <mutex>
using namespace std;
class LockGuard //自己封装一个智能锁
{
public:
LockGuard(mutex& lock) : _lock(lock)
{
_lock.lock();
cout << "加锁:" << this_thread::get_id() << endl;
}
~LockGuard()
{
cout << "解锁:" << this_thread::get_id() << endl;
_lock.unlock();
}
private:
mutex& _lock; //这里必须写一个 &,代表该变量在初始化列表中被定义为外部变量的引用(注意这里只是声明,因此可以使用引用)。在我之前的文章中,虽然有提到,但是没有遇到实际的应用场景,这里就是一个场景
};
int main()
{
int n = 5;
vector<thread> threads(5);
mutex mtx;
for (int i = 0; i < 5; ++i)
{
threads[i] = thread([&]() {
for (int i = 0; i < n; ++i)
{
LockGuard Lock(mtx);
cout << this_thread::get_id() << ":" << i << endl; //即便抛出异常也会自动解锁
this_thread::sleep_for(chrono::milliseconds(100));
}
}
);
}
for (auto& e : threads)
{
e.join();
}
return 0;
}//库内智能锁避免因异常出现死锁
#include <iostream>
#include <vector>
#include <thread>
#include <mutex>
using namespace std;
int main()
{
int m = 5;
int n = 5;
vector<thread> threads(m);
mutex mtx;
for (int i = 0; i < m; ++i)
{
threads[i] = thread( [&]() {
for (int i = 0; i < n; ++i)
{
lock_guard<mutex> Lock(mtx);
cout << this_thread::get_id() << ":" << i << endl;
this_thread::sleep_for(chrono::milliseconds(100));
}
}
);
}
for (auto& e : threads)
{
e.join();
}
return 0;
}2.5.unique_lock
在 lock_guard 的基础上多了可以进行手动提前释放锁,再进行加锁,通常需要在一些特殊场景里使用。
unique_lock 本身就是智能加锁和智能解锁,只不过在“某些需要长期使用锁资源的场景”或是“条件变量中途需要短暂解锁”时,需要手动释放一次锁再加回来(在后面的条件变量中,默认传递的锁就是 unique_lock)。
补充:更详细的说明和描述您可以去 cplusplus-mutex 上查询。
3.atomic 原子库
有些时候,所谓的公共资源仅仅是一个公共变量,对一个变量加锁的力度实际上是有点大的。如果我们直接保证对其的 ++/-- 等操作进行原子化,就可以减小锁的力度,也是无锁编程的一种方式,也被称为CAS 操作(无锁操作)。
//多线程内使用局部锁避免不同步
#include <iostream>
#include <vector>
#include <thread>
#include <mutex>
#include <functional> //std::ref(val),强制接受方变成 val 的左值引用
using namespace std;
int main()
{
vector<thread> threads;
mutex mtx;
int threadNum = 5;
int target = 100;
int time = 10;
int add = 0;
for (int i = 0; i < threadNum; i++)
{
threads.push_back(
thread( //注意这里只能使用匿名线程对象的移动构造(线程对象不允许拷贝),并且使用 lambda 表达式避免传参
[&]() { //使用 lambda 表达式
while (true)
{
mtx.lock();
if (add < target)
{
++add;
}
else
{
mtx.unlock();
break;
}
cout << " add = " << add << " id:" << this_thread::get_id() << endl;
mtx.unlock();
this_thread::sleep_for(chrono::milliseconds(time)); //休眠 10 微秒再输出
}
}
)
);
}
for (auto& e : threads)
e.join();
return 0;
}//多线程内使用原子操作
#include <iostream>
#include <vector>
#include <thread>
#include <mutex>
#include <atomic> //微小的操作可以不加锁,直接使用原子操作即可
using namespace std;
int main()
{
vector<thread> threads;
mutex mtx;
int threadNum = 5;
int target = 100;
int time = 10;
atomic<int> add = 0;
for (int i = 0; i < threadNum; i++)
{
threads.push_back(
thread( //注意这里只能使用匿名线程对象的移动构造(线程对象不允许拷贝),并且使用 lambda 表达式避免传参
[&]() { //使用 lambda 表达式
while (true)
{
if (add < target)
{
++add; //这里是原子操作返回寄存器时判断是否需要覆盖(这里是对 ++ 进行了重载)
}
else
{
break;
}
cout << " add = " << add << endl;
this_thread::sleep_for(chrono::milliseconds(time)); //休眠 10 微秒再输出
}
}
)
);
}
for (auto& e : threads)
e.join();
return 0;
}4.condition_variable 条件变量库
假设存在场景,需要在递增 i(初始值为0) 的同时,让线程 A 打印奇数,而线程 B 打印偶数。这种场景下,单纯的加锁确实可以解决二义性的问题,但两个线程都需要判断变量的状态(判断是奇还是偶再进行打印)。
假设线程 A 抢夺锁比线程 B 要快,就会陷入一种尴尬的状态:线程 A 一直申请锁检查变量 i,结果发现不是奇数,线程 A 又不能擅自把 B 减为偶数,只能退出解锁。线程 A 的速度大部分情况下都比线程 B 快,线程 A 经常对锁进行无效抢夺,线程 B 又时常陷入线程饥饿的状态。最后导致效率有可能变得很慢。
4.1.使用普通锁
//一个线程打印奇数,一个线程打印偶数(直接使用锁,一种错误的写法)
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;
int main()
{
int i = 0;
int n = 100;
mutex mtx;
thread t1([&]() {
while (i < n)
{
mtx.lock();
cout << this_thread::get_id() << ":" << i << endl;
i += 1;
mtx.unlock();
}
}
);
thread t2([&]() {
while (i < n)
{
mtx.lock();
cout << this_thread::get_id() << ":" << i << endl;
i += 1;
mtx.unlock();
}
}
);
t1.join();
t2.join();
return 0;
}//一个线程打印奇数,一个线程打印偶数(直接使用锁,一种正确的写法)
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;
int main()
{
int i = 0;
int n = 100;
mutex mtx;
thread t1([&]() {
while (i < n)
{
mtx.lock();
while (i % 2 != 0)
{
cout << this_thread::get_id() << ":" << i << endl;
i += 1;
}
mtx.unlock();
}
}
);
thread t2([&]() {
while (i < n)
{
mtx.lock();
while (i % 2 == 0)
{
cout << this_thread::get_id() << ":" << i << endl;
i += 1;
}
mtx.unlock();
}
}
);
t1.join();
t2.join();
return 0;
}4.2.使用自旋锁
这里使用的自旋锁实际上只是使用了 yield() 来实现。
//一个线程打印奇数,一个线程打印偶数(使用自旋锁)
#include <iostream>
#include <thread>
using namespace std;
/* 自旋锁的基本原理是当一个线程尝试获取锁时,
如果发现锁已经被其他线程占用,则该线程不会进入阻塞状态,
而是会以忙等的方式不断检查锁的状态,直到锁被释放。
因此,自旋锁适用于锁被持有时间短、并发度高的场景,
可以避免线程频繁地进入和退出阻塞状态所带来的性能开销。 */
int main()
{
int i = 0;
int n = 100;
thread t1([&]() {
while (i < n)
{
while (i % 2 != 0) //如果 i 是奇数
{
this_thread::yield(); //让出时间片,相当于自旋锁
}
cout << this_thread::get_id() << ":" << i << endl;
i += 1;
}
});
thread t2([&]() {
while (i < n)
{
while (i % 2 == 0) //如果 i 是偶数
{
this_thread::yield(); //让出时间片,相当于自旋锁
}
cout << this_thread::get_id() << ":" << i << endl;
i += 1;
}
});
t1.join();
t2.join();
return 0;
}在极端场景下线程 A 即便扔出时间片,也会导致该时间片无法被线程 B 抢夺,有可能变成线程 A 和线程 B 同时被阻塞。
4.3.使用条件变量
我们先使用第一种方法,注意避免伪唤醒的情况。
//一个线程打印奇数,一个线程打印偶数(使用条件变量)
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
using namespace std;
int main()
{
int i = 0;
int n = 100;
mutex mtx;
condition_variable cv; //条件变量
thread t1([&]() {
while (i < n)
{
unique_lock<mutex> lock(mtx);
cv.wait(lock); //先提前进入等待状态
if (i % 2 != 0) //是奇数
{
cout << this_thread::get_id() << ":" << i << endl;
i += 1;
}
else if (i == 99)
break;
}
});
thread t2([&]() {
while (i < n)
{
unique_lock<mutex> lock(mtx);
cv.wait(lock); //先提前进入等待状态
if (i % 2 == 0) //是偶数
{
cout << this_thread::get_id() << ":" << i << endl;
i += 1;
}
else if (i == 100)
break;
}
});
while (true)
{
cv.notify_one(); //主线程依次唤醒子线程
if (i == 100)
break;
}
t1.join();
t2.join();
return 0;
}第二种方法,可以使用 flag 来让子进程间互相唤醒。
//一个线程打印奇数,一个线程打印偶数(使用条件变量)
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
using namespace std;
int main()
{
int i = 0;
int n = 100;
mutex mtx;
condition_variable cv; //条件变量
bool flag = true;
thread t1([&]() {
while (i < n)
{
unique_lock<mutex> lock(mtx);
if (flag == true)
cv.wait(lock);
cout << this_thread::get_id() << ":" << i << endl;
i += 1;
flag = true;
cv.notify_one();
}
});
thread t2([&]() {
while (i < n)
{
unique_lock<mutex> lock(mtx);
if (flag == false) //就算是极端情况下,这个线程先抢到锁,第一次循环不会进入等待,先进行打印,然后第二次会进入等待
cv.wait(lock);
cout << this_thread::get_id() << ":" << i << endl;
i += 1;
flag = false;
cv.notify_one();
}
});
t1.join();
t2.join();
return 0;
}再尝试使用第三种方法,这种方法对于伪唤醒的处理更加简洁。
//一个线程打印奇数,一个线程打印偶数(使用条件变量)
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
using namespace std;
int main()
{
int i = 0;
int n = 100;
mutex mtx;
condition_variable cv; //条件变量
bool ready = true;
thread t1([&]() {
while (i < n)
{
unique_lock<mutex> lock(mtx);
cv.wait(lock, [&]() { return !ready; }); //这里的第二个 lambda 表达式返回值为 false 时就会一直等待,可以用来避免伪唤醒的情况(另外,可调用的对象也被叫做 predicate(谓语) 描述动作或状态)
cout << this_thread::get_id() << ":" << i << endl;
i += 1;
ready = true; //加完后就让变量 ready 为真,这样第二个线程就可以在唤醒后通过 predicate 返回真直接往下执行
cv.notify_one(); //唤醒其他线程 notify_all() 就是唤醒所有的因为条件变量等待的线程
}
});
thread t2([&]() {
while (i < n)
{
unique_lock<mutex> lock(mtx);
cv.wait(lock, [&]() { return ready; }); //返回值为 false 就会一直等待,并且在那之前先解锁
cout << this_thread::get_id() << ":" << i << endl;
i += 1;
ready = false;
cv.notify_one(); //唤醒其他线程
}
});
t1.join();
t2.join();
return 0;
}补充:更详细的说明和描述您可以去 cplusplus-condition_variable 上查询。
注意,轻易不要使用 notify_all(),容易引发“惊群”的现象。
5.C++的线程安全问题
智能指针和多线程结合使用可能会出现一些问题。
在我们之前实现的 shared_ptr 中还有一个隐藏问题只是泛泛提及过,这里使用代码详细解读。
//自定义的 shared_ptr 头文件和实现
#include <iostream>
using namespace std;
template <typename Type>
class SmartPtr
{
public:
SmartPtr(Type* ptr)
: _ptr(ptr), _pcount(new int(1))
{}
void _release()
{
if (--(*_pcount) == 0)
{
delete _ptr;
delete _pcount;
}
}
~SmartPtr()
{
_release();
}
SmartPtr(const SmartPtr<Type>& sp)
: _ptr(sp._ptr), _pcount(sp._pcount)
{
++(*_pcount);
}
SmartPtr<Type>& operator=(const SmartPtr<Type>& sp)//这个代码需要注意一下
{
_release();
_ptr = sp._ptr;
_pcount = sp._pcount;
++(*_pcount);
return *this;
}
Type& operator*()
{
return *_ptr;
}
Type* operator->()
{
return _ptr;
}
int use_count()
{
return *_pcount;
}
private:
Type* _ptr;
int* _pcount;
};//使用自定义的 shared_ptr 结合多线程
#include <iostream>
#include <thread>
#include <vector>
#include "mySharedPtr.hpp" //导入我们自己写的 shared_ptr 头文件
using namespace std;
int main()
{
SmartPtr<int> sp1(new int(1));
SmartPtr<int> sp2(sp1);
vector<thread> v(4);
int n = 100000;
for (auto& t : v)
{
t = thread([&]() {
for (size_t i = 0; i < n; ++i)
{
SmartPtr<int> sp(sp1); //由于复制不断增加引用计数,而引用计数本身不是线程安全的!
}
}
);
}
for (auto& t : v)
{
t.join();
}
cout << sp1.use_count() << endl;
return 0;
}
/* 运行结果
多次运行的结果不唯一(尤其是线程数量较多的时候)
*/如果我们切换为库内的 shared_ptr 就发现不会有这个问题,这说明库内的 shared_ptr 中的引用计数应该是原子操作,我们来试试使用原子库优化我们模拟的 shared_ptr:
//使用原子操作引用计数的 shared_ptr
#include <iostream>
#include <atomic>
using namespace std;
template <typename Type>
class SmartPtr
{
public:
SmartPtr(Type* ptr)
: _ptr(ptr), _pcount(new atomic<int>(1))
{}
void _release()
{
if (--(*_pcount) == 0)
{
delete _ptr;
delete _pcount;
}
}
~SmartPtr()
{
_release();
}
SmartPtr(const SmartPtr<Type>& sp)
: _ptr(sp._ptr), _pcount(sp._pcount)
{
++(*_pcount);
}
SmartPtr<Type>& operator=(const SmartPtr<Type>& sp)//这个代码需要注意一下
{
_release();
_ptr = sp._ptr;
_pcount = sp._pcount;
++(*_pcount);
return *this;
}
Type& operator*()
{
return *_ptr;
}
Type* operator->()
{
return _ptr;
}
int use_count()
{
return *_pcount;
}
private:
Type* _ptr;
atomic<int>* _pcount; //改为原子计数
};#include <iostream>
#include <thread>
#include <vector>
#include "mySharedPtr.hpp" //导入我们自己写的 shared_ptr 头文件
using namespace std;
int main()
{
SmartPtr<int> sp1(new int(1));
SmartPtr<int> sp2(sp1);
vector<thread> v(5);
int n = 1000000;
for (auto& t : v)
{
t = thread([&]() {
for (size_t i = 0; i < n; ++i)
{
SmartPtr<int> sp(sp1); //由于复制不断增加引用计数,而引用计数本身不是线程安全的!
}
}
);
}
for (auto& t : v)
{
t.join();
}
cout << sp1.use_count() << endl;
return 0;
}
/* 运行结果
多次运行的结果唯一,始终输出为 2
*/当然也可以尝试加锁。
但是如果我们自定义的 hared_ptr 执行的资源是否线程安全的呢(比如解引用后做了 ++ 操作)?
答案是不是,无论是加锁还是原子操作,我们保护的都是引用计数,而解引用后的资源无法保证线程安全,并且也很难优化 hared_ptr 来解决这个问题。
//自定义的 hared_ptr 头文件
#include <iostream>
#include <atomic>
using namespace std;
template <typename Type>
class SmartPtr
{
public:
SmartPtr(Type* ptr)
: _ptr(ptr), _pcount(new atomic<int>(1))
{}
void _release()
{
if (--(*_pcount) == 0)
{
delete _ptr;
delete _pcount;
}
}
~SmartPtr()
{
_release();
}
SmartPtr(const SmartPtr<Type>& sp)
: _ptr(sp._ptr), _pcount(sp._pcount)
{
++(*_pcount);
}
SmartPtr<Type>& operator=(const SmartPtr<Type>& sp)//这个代码需要注意一下
{
_release();
_ptr = sp._ptr;
_pcount = sp._pcount;
++(*_pcount);
return *this;
}
Type& operator*()
{
return *_ptr;
}
Type* operator->()
{
return _ptr;
}
int use_count()
{
return *_pcount;
}
private:
Type* _ptr;
atomic<int>* _pcount; //改为原子计数
};//对智能指针指向的资源进行操作
#include <iostream>
#include <thread>
#include <vector>
#include "mySharedPtr.hpp" //导入我们自己写的 shared_ptr 头文件
using namespace std;
int main()
{
SmartPtr<int> sp1(new int(1));
SmartPtr<int> sp2(sp1);
vector<thread> v(5);
int n = 1000000;
for (auto& t : v)
{
t = thread([&]() {
for (size_t i = 0; i < n; ++i)
{
SmartPtr<int> sp(sp1); //由于复制不断增加引用计数,而引用计数本身不是线程安全的!
(*sp)++;
}
}
);
}
for (auto& t : v)
{
t.join();
}
cout << *sp1 << endl;
return 0;
}
/* 运行结果
多次运行的结果不唯一(尤其是线程数量较多的时候)
*/但是用户可以自己加锁,保证线程安全:
//自定义的 hared_ptr 头文件
#include <iostream>
#include <atomic>
using namespace std;
template <typename Type>
class SmartPtr
{
public:
SmartPtr(Type* ptr)
: _ptr(ptr), _pcount(new atomic<int>(1))
{}
void _release()
{
if (--(*_pcount) == 0)
{
delete _ptr;
delete _pcount;
}
}
~SmartPtr()
{
_release();
}
SmartPtr(const SmartPtr<Type>& sp)
: _ptr(sp._ptr), _pcount(sp._pcount)
{
++(*_pcount);
}
SmartPtr<Type>& operator=(const SmartPtr<Type>& sp)//这个代码需要注意一下
{
_release();
_ptr = sp._ptr;
_pcount = sp._pcount;
++(*_pcount);
return *this;
}
Type& operator*()
{
return *_ptr;
}
Type* operator->()
{
return _ptr;
}
int use_count()
{
return *_pcount;
}
private:
Type* _ptr;
atomic<int>* _pcount; //改为原子计数
};//用户对智能指针指向的资源加锁
#include <iostream>
#include <thread>
#include <vector>
#include <mutex>
#include "mySharedPtr.hpp" //导入我们自己写的 shared_ptr 头文件
using namespace std;
int main()
{
mutex mtx;
SmartPtr<int> sp1(new int(1));
SmartPtr<int> sp2(sp1);
vector<thread> v(5);
int n = 1000000;
for (auto& t : v)
{
t = thread([&]() {
for (size_t i = 0; i < n; ++i)
{
SmartPtr<int> sp(sp1); //由于复制不断增加引用计数,而引用计数本身不是线程安全的!
mtx.lock();
(*sp)++;
mtx.unlock();
}
}
);
}
for (auto& t : v)
{
t.join();
}
cout << *sp1 << endl;
return 0;
}
/* 运行结果
多次运行的结果唯一(恒为 50000001)
*/而实际上,同样的问题在 STL 中比比皆是(尽管 STL 的实现是及其精妙的泛型编程)。