线程池封装
目录
线程池设计
线程池封装
V1版
线程安全的单例模式
单例模式的特点
饿汉实现方式和懒汉实现方式
懒汉方式实现单例模式(线程安全版本)
线程安全和重入问题
常见锁概念
STL,智能指针和线程安全
线程池设计
线程池:
一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。
线程池的应用场景:
需要大量的线程来完成任务,且完成任务的时间比较短。 比如WEB服务器完成网页请求这样的任务,使用线程池技术是非常合适的。因为单个任务小,而任务数量巨大,你可以想象一个热门网站的点击次数。 但对于长时间的任务,比如⼀个Telnet连接请求,线程池的优点就不明显了。因为
Telnet会话时间比线程的创建时间大多了。对性能要求苛刻的应用,比如要求服务器迅速响应客⼾请求。接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。突发性大量客户请求,在没有线程池情况下,将产生大量线程,虽然理论上大部分操作系统线程数目最大值不是问题,短时间内产生大量线程可能使内存到达极限,出现错误
线程池的种类
a. 创建固定数量线程池,循环从任务队列中获取任务对象,获取到任务对象后,执行任务对象中
的任务接口
b. 浮动线程池,其他同上
此处,我们选择固定线程个数的线程池。
线程池封装
V1版
Log.hpp
#ifndef __LOG_HPP__
#define __LOG_HPP__#include <iostream>
#include <string>
#include "Mutex.hpp"
#include <filesystem>
#include <fstream>
#include <memory>
#include <unistd.h>
#include <sstream>
#include<ctime>namespace LogModule
{const std::string sep = "\r\n";using namespace MutexModule;// 2.刷新策略class LogStrategy{public:~LogStrategy() = default;virtual void SyncLog(const std::string &message) = 0;};// 显示器刷新日志的策略class ConsoleLogStrategy : public LogStrategy{public:ConsoleLogStrategy() {}~ConsoleLogStrategy() {}void SyncLog(const std::string &message) override{LockGuard lockguard(_mutex);std::cout << message << sep;}private:Mutex _mutex;};// 缺省文件路径以及文件本身const std::string defaultpath = "./log";const std::string defaultfile = "my.log";// 文件刷新日志的策略class FileLogStrategy : public LogStrategy{public:FileLogStrategy(const std::string &path = defaultpath, const std::string &file = defaultfile): _path(path), _file(file){LockGuard lockguard(_mutex);if (std::filesystem::exists(_path)) // 判断路径是否存在{return;}try{std::filesystem::create_directories(_path);}catch (const std::filesystem::filesystem_error &e){std::cerr << e.what() << '\n';}}void SyncLog(const std::string &message) override{LockGuard lockguard(_mutex);std::string filename = _path + (_path.back() == '/' ? "" : "/") + _file;std::ofstream out(filename, std::ios::app); // 追加写入if (!out.is_open()){return;}out << message << sep;out.close();}~FileLogStrategy() {}private:Mutex _mutex;std::string _path; // 日志文件的路径std::string _file; // 要打印的日志文件};// 形成日志等级enum class Loglevel{DEBUG,INFO,WARNING,ERROR,FATAL};std::string Level2Str(Loglevel level){switch (level){case Loglevel::DEBUG:return "DEBUG";case Loglevel::INFO:return "INFO";case Loglevel::WARNING:return "WARNING";case Loglevel::ERROR:return "ERROR";case Loglevel::FATAL:return "FATAL";default:return "UNKNOWN";}}std::string GetTimeStamp(){time_t cuur =time(nullptr);struct tm curr_tm;localtime_r(&cuur,&curr_tm);char buffer[128];snprintf(buffer,sizeof(buffer),"%4d-%02d-%02d %02d:%02d:%02d",curr_tm.tm_year+1900,curr_tm.tm_mon+1,curr_tm.tm_mday,curr_tm.tm_hour,curr_tm.tm_min,curr_tm.tm_sec);return buffer;}class Logger{public:Logger(){EnableConsoleLogStrategy();}// 选择某种策略// 1.文件void EnableFileLogStrategy(){_ffush_strategy = std::make_unique<FileLogStrategy>();}// 显示器void EnableConsoleLogStrategy(){_ffush_strategy = std::make_unique<ConsoleLogStrategy>();}// 表示的是未来的一条日志class LogMessage{public:LogMessage(Loglevel &level, std::string &src_name, int line_number, Logger &logger): _curr_time(GetTimeStamp()), _level(level), _pid(getpid()), _src_name(src_name), _line_number(line_number), _logger(logger){// 合并左半部分std::stringstream ss;ss << "[" << _curr_time << "] "<< "[" << Level2Str(_level) << "] "<< "[" << _pid << "] "<< "[" << _src_name << "] "<< "[" << _line_number << "] "<< "- ";_loginfo = ss.str();}template <typename T>LogMessage &operator<<(const T &info){// 右半部分,可变std::stringstream ss;ss << info;_loginfo += ss.str();return *this;}~LogMessage(){if (_logger._ffush_strategy){_logger._ffush_strategy->SyncLog(_loginfo);}}private:std::string _curr_time; // 日志时间Loglevel _level; // 日志状态pid_t _pid; // 进程pidstd::string _src_name; // 文件名称int _line_number; // 对应的行号std::string _loginfo; // 合并之后的一条完整信息Logger &_logger;};LogMessage operator()(Loglevel level, std::string src_name, int line_number){return LogMessage(level, src_name, line_number, *this);}~Logger() {}private:std::unique_ptr<LogStrategy> _ffush_strategy;};//全局日志对象Logger logger;//使用宏,简化用户操作,获取文件名和行号// __FILE__ 一个宏,替换完成后目标文件的文件名// __LINE__ 一个宏,替换完成后目标文件对应的行号#define LOG(level) logger(level,__FILE__,__LINE__) #define Enable_Console_Log_Strategy() logger.EnableConsoleLogStrategy()#define Enable_File_Log_Strategy() logger.EnableFileLogStrategy()}#endif
Cond,hpp
#include <iostream>
#include<pthread.h>
#include"Mutex.hpp"
using namespace std;
using namespace MutexModule;
namespace CondModule
{class Cond{public:Cond(){pthread_cond_init(&_cond,nullptr);}~Cond(){pthread_cond_destroy(&_cond);}void Wait(Mutex &_mutex){ int n=pthread_cond_wait(&_cond,_mutex.get());(void)n;}//唤醒下一个在条件变量下等待线程void Signal(){pthread_cond_signal(&_cond);}//唤醒所有在条件变量下等待的线程void Broadcast(){int n=pthread_cond_broadcast(&_cond);(void)n;}private:pthread_cond_t _cond;};};
Task.hpp
#pragma once
#include<iostream>
#include<unistd.h>
#include"Log.hpp"
#include <functional>using namespace LogModule;using task_t =std::function<void()> ;void Download()
{LOG(Loglevel::DEBUG)<<"我是一个下载任务";
}
Tthred.hpp
#include <iostream>
#include <string>
#include <pthread.h>
#include <cstdio>
#include <cstring>
#include <functional>namespace ThreadModlue
{static uint32_t number = 1;class Thread{using func_t = std::function<void()>;public:Thread(func_t func): _tid(0),_isdetach(false),_isrunning(false),res(nullptr),_func(func){_name = "thread-" + std::to_string(number++);}~Thread(){}static void *routine(void *args){Thread *self = static_cast<Thread *>(args);self->EnableRunning();if (self->_isdetach)self->Detach();pthread_setname_np(self->_tid,self->_name.c_str());self->_func();return nullptr;}void EnableDetach(){if (_isrunning)return;_isdetach = true;}void EnableRunning(){_isrunning = true;}void Detach(){if (_isdetach){return;}if (_isrunning)pthread_detach(_tid);EnableDetach();}bool Stop(){if (_isrunning){int n = pthread_cancel(_tid);if (n != 0){std::cerr << "Stop thread error:" << strerror(n) << std::endl;return false;}else{_isrunning = false;std::cout << _name << " stop" << std::endl;}}return true;}bool Start(){int n = pthread_create(&_tid, nullptr, routine, this);if (n > 0){std::cerr << "create thread error:" << strerror(n) << std::endl;return false;}else{}return true;}std::string Name(){return _name;}void Join(){if (_isdetach)return;if (_isrunning){int n = pthread_join(_tid, &res);if (n != 0){std::cerr << "Join thread error:" << strerror(n) << std::endl;}}else{std::cout << "join success!" << std::endl;}}private:pthread_t _tid;std::string _name;bool _isdetach;bool _isrunning;void *res;func_t _func;};
}
ThreadPool.hpp
#include <iostream>
#include <string>
#include "Log.hpp"
#include "Thread.hpp"
#include <vector>
#include <queue>
#include "Cond.hpp"
#include "Mutex.hpp"namespace ThreadPoolModule
{using namespace ThreadPoolModule;using namespace LogModule;using namespace CondModule;using namespace MutexModule;using namespace ThreadModlue;static const int gnum = 5;template <typename T>class ThreadPool{private:// 唤醒所有在条件变量下等待的线程void WakeUpAllThread(){_cond.Broadcast();LOG(Loglevel::INFO) << "唤醒所有线程";}public:ThreadPool(int num = gnum): _num(num), _isrunning(false), _sleepernum(0){for (int i = 0; i < num; i++){_threads.emplace_back([this](){HandlerTask();});}}void Join(){for (auto &thread : _threads){thread.Join();}}void HandlerTask(){while (true) { T t;char name[1024];pthread_getname_np(pthread_self(), name, sizeof(name));{LockGuard lockguard(_mutex);// 队列为空,线程池没有退出while (_teskq.empty() && _isrunning){_sleepernum++;_cond.Wait(_mutex);_sleepernum--;}// 被唤醒时,没有任务了,线程在休眠if (!_isrunning && _teskq.empty()){LOG(Loglevel::INFO) << name << "退出,线程池退出&&任务为空";break;}// 被唤醒 有任务t = _teskq.front();_teskq.pop();}t(); // 处理任务 任务被拿出来后,是线程私有的,加快效率,把它拿出来在锁外面出理}}// 入任务bool Enqueue(const T &in){if (_isrunning){LockGuard lockguard(_mutex);_teskq.push(in);if (_threads.size() == _sleepernum) // 没有一个线程在工作,唤醒一个线程WakeUpOne();return true;}return false;}// 唤醒一个线程void WakeUpOne(){_cond.Signal();LOG(Loglevel::INFO) << "唤醒一个线程";}// 让线程启动void Start(){if (_isrunning)return;_isrunning = true;for (auto &thread : _threads){thread.Start();LOG(Loglevel::INFO) << "start thread new thread success!" << thread.Name();}}// 让线程停止void Stop(){if (!_isrunning)return;_isrunning = false;// 唤醒所有线程,防止任务队列为空,线程无法醒来WakeUpAllThread();}~ThreadPool() {}private:std::vector<Thread> _threads; // 放线程int _num; // 线程池中线程个数std::queue<T> _teskq; // 任务队列Cond _cond; // 不满足条件时继续等Mutex _mutex; // 互斥锁,保证原子性bool _isrunning; // 标记线程是否运行int _sleepernum; // 当前有多少个休眠线程};}
Mutex.hpp
#pragma once#include <pthread.h>
#include <iostream>
namespace MutexModule
{class Mutex{public:Mutex(){pthread_mutex_init(&_mutex, nullptr);}void Lock(){int n = pthread_mutex_lock(&_mutex);(void)n;}void Unlock(){int n = pthread_mutex_unlock(&_mutex);(void)n;}~Mutex(){pthread_mutex_destroy(&_mutex);}pthread_mutex_t *get(){return &_mutex;}private:pthread_mutex_t _mutex;};class LockGuard{public:LockGuard(Mutex &mutex):_mutex(mutex){_mutex.Lock();}~LockGuard(){_mutex.Unlock();}private:Mutex &_mutex;};
}
Main.cc
#include "Log.hpp"
#include"ThreadPool.hpp"
#include<memory>
#include"Task.hpp"
using namespace LogModule;
using namespace ThreadPoolModule;int main(){ Enable_Console_Log_Strategy();ThreadPool<task_t> *tp = new ThreadPool<task_t>();tp->Start();sleep(3);int count=10;
while(count)
{tp->Enqueue(Download);sleep(1);count--;
}tp->Stop();tp->Join();return 0;
}
MAkefile
threadpool:Main.ccg++ -o $@ $^ -std=c++17 -lpthread
.PHONY:clean
clean:rm -f threadpool
线程安全的单例模式
单例模式的特点
某些类, 只应该具有一个对象(实例), 就称之为单例.例如一个男人只能有一个媳妇.
在很多服务器开发场景中, 经常需要让服务器加载很多的数据 (上百G) 到内存中. 此时往往要⽤一个单例的类来管理这些数据.
饿汉实现方式和懒汉实现方式
[洗碗的例子]
- 吃完饭, 立刻洗碗, 这种就是饿汉方式. 因为下一顿吃的时候可以立刻拿着碗就能吃饭.
- 吃完饭, 先把碗放下, 然后下一顿饭用到这个碗了再洗碗, 就是懒汉方式.
懒汉方式最核心的思想是 "延时加载". 从而能够优化服务器的启动速度.
饿汉方式实现单例模式
template <typename T>
class Singleton {static T data;
public:static T* GetInstance() {return &data;}
};
只要通过 Singleton 这个包装类来使用 T 对象, 则一个进程中只有一个 T 对象的实例.
懒汉方式实现单例模式
template <typename T>
class Singleton {static T* inst;
public:static T* GetInstance() {if (inst == NULL) {inst = new T();}return inst;}
};
存在一个严重的问题, 线程不安全.
第一次调用 GetInstance 的时候, 如果两个线程同时调用, 可能会创建出两份 T 对象的实例.
但是后续再次调用, 就没有问题了.
懒汉方式实现单例模式(线程安全版本)
1.把默认构造设为私有
2.把其他构造禁用
3.提供静态单例指针
4.提供静态返回线程池对象
修改上述ThreadPool.hpp,其余不变线程池2.0
#include <iostream>
#include <string>
#include "Log.hpp"
#include "Thread.hpp"
#include <vector>
#include <queue>
#include "Cond.hpp"
#include "Mutex.hpp"namespace ThreadPoolModule
{using namespace ThreadPoolModule;using namespace LogModule;using namespace CondModule;using namespace MutexModule;using namespace ThreadModlue;static const int gnum = 5;template <typename T>class ThreadPool{private:// 让线程启动void Start(){if (_isrunning)return;_isrunning = true;for (auto &thread : _threads){thread.Start();LOG(Loglevel::INFO) << "start thread new thread success!" << thread.Name();}}ThreadPool(int num = gnum): _num(num), _isrunning(false), _sleepernum(0){for (int i = 0; i < num; i++){_threads.emplace_back([this](){HandlerTask();});}}ThreadPool(const ThreadPool<T>& ) = delete;ThreadPool<T>& operator= ( const ThreadPool<T>& ) = delete;// 唤醒所有在条件变量下等待的线程void WakeUpAllThread(){_cond.Broadcast();LOG(Loglevel::INFO) << "唤醒所有线程";}public:void Join(){for (auto &thread : _threads){thread.Join();}}void HandlerTask(){while (true){T t;char name[1024];pthread_getname_np(pthread_self(), name, sizeof(name));{LockGuard lockguard(_mutex);// 队列为空,线程池没有退出while (_teskq.empty() && _isrunning){_sleepernum++;_cond.Wait(_mutex);_sleepernum--;}// 被唤醒时,没有任务了,线程在休眠if (!_isrunning && _teskq.empty()){LOG(Loglevel::INFO) << name << "退出,线程池退出&&任务为空";break;}// 被唤醒 有任务t = _teskq.front();_teskq.pop();}t(); // 处理任务 任务被拿出来后,是线程私有的,加快效率,把它拿出来在锁外面出理}}// 入任务bool Enqueue(const T &in){if (_isrunning){LockGuard lockguard(_mutex);_teskq.push(in);if (_threads.size() == _sleepernum) // 没有一个线程在工作,唤醒一个线程WakeUpOne();return true;}return false;}// 唤醒一个线程void WakeUpOne(){_cond.Signal();LOG(Loglevel::INFO) << "唤醒一个线程";}// 让线程停止void Stop(){if (!_isrunning)return;_isrunning = false;// 唤醒所有线程,防止任务队列为空,线程无法醒来WakeUpAllThread();}~ThreadPool() {}static ThreadPool<T> *GetInstance(){if (inc == nullptr){inc = new ThreadPool<T>();inc->Start();}return inc;}private:std::vector<Thread> _threads; // 放线程int _num; // 线程池中线程个数std::queue<T> _teskq; // 任务队列Cond _cond; // 不满足条件时继续等Mutex _mutex; // 互斥锁,保证原子性bool _isrunning; // 标记线程是否运行int _sleepernum; // 当前有多少个休眠线程static ThreadPool<T> *inc; // 单例指针};template <typename T>ThreadPool<T> *ThreadPool<T>::inc = nullptr;
}
Main.cc
#include "Log.hpp"
#include"ThreadPool.hpp"
#include<memory>
#include"Task.hpp"
using namespace LogModule;
using namespace ThreadPoolModule;int main(){ Enable_Console_Log_Strategy();//以下禁用//ThreadPool<task_t> tp ;//ThreadPool<task_t> tp1=tp ;//ThreadPool<task_t> tp2=new ThreadPool<task_t> ();int count=10;
while(count)
{ThreadPool<task_t>::GetInstance()->Enqueue(Download);sleep(1);count--;
}ThreadPool<task_t>::GetInstance()->Stop();ThreadPool<task_t>::GetInstance()->Join();return 0;
}
2.0是单生产多消费模型,但是多生产多消费就有问题,得加锁,3.0:
ThreadPool.hpp
#include <iostream>
#include <string>
#include "Log.hpp"
#include "Thread.hpp"
#include <vector>
#include <queue>
#include "Cond.hpp"
#include "Mutex.hpp"namespace ThreadPoolModule
{using namespace ThreadPoolModule;using namespace LogModule;using namespace CondModule;using namespace MutexModule;using namespace ThreadModlue;static const int gnum = 5;template <typename T>class ThreadPool{private:// 让线程启动void Start(){if (_isrunning)return;_isrunning = true;for (auto &thread : _threads){thread.Start();LOG(Loglevel::INFO) << "start thread new thread success!" << thread.Name();}}ThreadPool(int num = gnum): _num(num), _isrunning(false), _sleepernum(0){for (int i = 0; i < num; i++){_threads.emplace_back([this](){HandlerTask();});}}ThreadPool(const ThreadPool<T> &) = delete;ThreadPool<T> &operator=(const ThreadPool<T> &) = delete;// 唤醒所有在条件变量下等待的线程void WakeUpAllThread(){_cond.Broadcast();LOG(Loglevel::INFO) << "唤醒所有线程";}public:void Join(){for (auto &thread : _threads){thread.Join();}}void HandlerTask(){while (true){T t;char name[1024];pthread_getname_np(pthread_self(), name, sizeof(name));{LockGuard lockguard(_mutex);// 队列为空,线程池没有退出while (_teskq.empty() && _isrunning){_sleepernum++;_cond.Wait(_mutex);_sleepernum--;}// 被唤醒时,没有任务了,线程在休眠if (!_isrunning && _teskq.empty()){LOG(Loglevel::INFO) << name << "退出,线程池退出&&任务为空";break;}// 被唤醒 有任务t = _teskq.front();_teskq.pop();}t(); // 处理任务 任务被拿出来后,是线程私有的,加快效率,把它拿出来在锁外面出理}}// 入任务bool Enqueue(const T &in){if (_isrunning){LockGuard lockguard(_mutex);_teskq.push(in);if (_threads.size() == _sleepernum) // 没有一个线程在工作,唤醒一个线程WakeUpOne();return true;}return false;}// 唤醒一个线程void WakeUpOne(){_cond.Signal();LOG(Loglevel::INFO) << "唤醒一个线程";}// 让线程停止void Stop(){if (!_isrunning)return;_isrunning = false;// 唤醒所有线程,防止任务队列为空,线程无法醒来WakeUpAllThread();}~ThreadPool() {}static ThreadPool<T> *GetInstance(){LockGuard lockguard(_lock);LOG(Loglevel::INFO) << "创建单例...";if (inc == nullptr){LOG(Loglevel::INFO) << "首次创建单例,创建之...";inc = new ThreadPool<T>();inc->Start();}return inc;}private:std::vector<Thread> _threads; // 放线程int _num; // 线程池中线程个数std::queue<T> _teskq; // 任务队列Cond _cond; // 不满足条件时继续等Mutex _mutex; // 互斥锁,保证原子性bool _isrunning; // 标记线程是否运行int _sleepernum; // 当前有多少个休眠线程static ThreadPool<T> *inc; // 单例指针static Mutex _lock; // 保护单例的锁};// 类内静态变量类外初始化template <typename T>ThreadPool<T> *ThreadPool<T>::inc = nullptr;template <typename T>Mutex ThreadPool<T>::_lock;
}
线程安全和重入问题
概念
线程安全:就是多个线程在访问共享资源时,能够正确地执行,不会相互干扰或破坏彼此的执行结
果。一般而言,多个线程并发同一段只有局部变量的代码时,不会出现不同的结果。但是对全局变量或者静态变量进行操作,并且没有锁保护的情况下,容易出现该问题。
重入:同⼀个函数被不同的执行流调用,当前一个流程还没有执行完,就有其他的执行流再次进入,我们称之为重入。一个函数在重入的情况下,运行结果不会出现任何不同或者任何问题,则该函数被称为可重入函数,否则,是不可重入函数。
学到现在,其实我们已经能理解重入其实可以分为两种情况
- 多线程重入函数
- 信号导致一个执行流重复进入函数
常见的线程不安全的情况
- 不保护共享变量的函数
- 函数状态随着被调用,状态发生变化的函数
- 返回指向静态变量指针的函数
- 调用线程不安全函数的函数
常见的线程安全的情况
- 每个线程对全局变量或者静态变量只有读取的权限,而没有写入的权限,一般来说这些线程是安全的
- 类或者接口对于线程来说都是原子操作
- 多个线程之间的切换不会导致该接口的执行结果存在⼆义性
常见不可重入的情况
- 调用了malloc/free函数,因为malloc函数是用全局链表来管理堆的
- 调⽤了标准I/O库函数,标准I/O库的很多实现都以不可重入的方式使用全局数据结构
- 可重入函数体内使用了静态的数据结构
常见可重入的情况
- 不使用全局变量或静态变量
- 不使用用malloc或者new开辟出的空间
- 不调用不可重入函数
- 不返回静态或全局数据,所有数据都有函数的调用者提供
- 使用本地数据,或者通过制作全局数据的本地拷贝来保护全局数据
结论
不要被上面绕口令式的话语唬住,你只要仔细观察,其实对应概念说的都是一回事。
可重入与线程安全联系
函数是不可重入的,那就不能由多个线程使用,有可能引发线程安全问题。如果一个函数中有全局变量,那么这个函数既不是线程安全也不是可重入的。
可重入与线程安全区别
可重⼊函数是线程安全函数的一种
线程安全不一定是可重入的,而可重入函数则一定是线程安全的。如果将对临界资源的访问加上锁,则这个函数是线程安全的,但如果这个重入函数若锁还未释放则会产生死锁,因此是不可重入的。
注意:
如果不考虑 信号导致一个执行流重复进入函数 这种重入情况,线程安全和重入在安全角度不做区分,但是线程安全侧重说明线程访问公共资源的安全情况,表现的是并发线程的特点,可重入描述的是一个函数是否能被重复进入,表示的是函数的特点
常见锁概念
死锁
死锁是指在⼀组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程所站用不会
释放的资源而处于的一种永久等待状态。为了方便表述,假设现在线程A,线程B必须同时持有锁1和锁2,才能进行后续资源的访问
申请一把锁是原子的,但是申请两把锁就不⼀定了
造成的结果是
死锁四个必要条件
- 互斥条件:一个资源每次只能被一个执行流使用
- 请求与保持条件:一个执行流因请求资源而阻塞时,对已获得的资源保持不放
- 不剥夺条件:一个执行流已获得的资源,在末使用完之前,不能强行剥夺
- 循环等待条件:若干执行流之间形成一种头尾相接的循环等待资源的关系
STL,智能指针和线程安全
STL中的容器是否是线程安全的?
不是.原因是, STL 的设计初衷是将性能挖掘到极致, ⽽⼀旦涉及到加锁保证线程安全, 会对性能造成巨大的影响.而且对于不同的容器, 加锁方式的不同, 性能可能也不同(例如hash表的锁表和锁桶).
因此 STL 默认不是线程安全. 如果需要在多线程环境下使用, 往往需要调用者自行保证线程安全.
智能指针是否是线程安全的?
对于 unique_ptr, 由于只是在当前代码块范围内⽣效, 因此不涉及线程安全问题.
对于 shared_ptr, 多个对象需要共用一个引用计数变量, 所以会存在线程安全问题. 但是标准库实现的时候考虑到了这个问题, 基于原⼦操作(CAS)的方式保证 shared_ptr 能够高效, 原子的操作引用计数.