线程池 引入

日志和线程池
预制菜
内存池-------->减少系统调用的次数(效率高)
malloc或者stl里面的扩容操作:本质都是在向OS要内存->系统调用->是由成本的(事件成本)
结论:线程池也是一种"池化"技术
日志概念:
printf cout:向显示器文件上打
⽇志认识
计算机中的⽇志是记录系统和软件运⾏中发⽣事件的⽂件,主要作⽤是监控运⾏状态、记录异常信 息,帮助快速定位问题并⽀持程序员进⾏问题修复。它是系统维护、故障排查和安全管理的重要⼯ 具。
⽇志格式以下⼏个指标是必须得有的
• 时间戳
• ⽇志等级
• ⽇志内容 以下⼏个指标是可选的
• ⽂件名⾏号
• 进程,线程相关id信息等
日志等级:
DEBUG:测试
INFO:常规
WARING:报警
ERROR:错误
FATAL:致命错误
日志是衡量软件健康状态的信息。
日志等级是衡量的指标。
日志功能:
1.形成完整的日志
2.刷新到目标文件(显示器、指定文件打印日志)
策略模式:确定是显示器还是文件
日志代码:
cpp
#pragma once
#include <iostream>
#include <cstdio>
#include <string>
#include <filesystem> //C++17
#include <sstream>
#include <fstream>
#include <memory>
#include <ctime>
#include <unistd.h>
#include "Mutex.hpp"
namespace LogModule
{
using namespace MutexModule;
using namespace std;
const string gesp = "\r\n"; //日志的经典后缀
// 线程池的策略
class LogStrategy
{
public:
~LogStrategy() = default; // 默认析构
virtual void SyncLog(const string &message) = 0; // 纯虚函数,自身没有实现方法
};
// 显示器显示
class ConsoleLogStrategy : public LogStrategy //继承
{
public:
ConsoleLogStrategy() {}
void SyncLog(const string &message) override //重写
{
LockGuard lockguard(_mutex);
cout << message << gesp;
}
~ConsoleLogStrategy() {};
private:
Mutex _mutex; // 显示器是资源应当上锁
};
// 文件指定写入
const string depath = "./log";
const string defilename = "my.log";
class FileLogStrategy : public LogStrategy
{
public:
FileLogStrategy(string path = depath, string filename = defilename)//设定默认的路径和名字
: _path(path),
_filename(filename)
{
if (filesystem::exists(_path))//检验是否存在该路径,存在继续,不存在创建
{
return;
}
try
{
filesystem::create_directories(_path); //创建该路径
}
catch (const filesystem::filesystem_error &e) //创建错误
{
cerr << e.what() << '\n';
}
}
void SyncLog(const string &message)
{
LockGuard lockguard(_mutex);
string filename = _path + (_path.back() == '/' ? "" : "/") + _filename;
ofstream out(filename, ios::app); // 追加的方式打开写入
out << message << gesp; //向里面写入
out.close(); //关闭文件
}
private:
string _path; // 路径
string _filename; // 文件名
Mutex _mutex;
};
enum class LogLevel //日志等级
{
DEBUG,
INFO,
WARRING,
ERROR,
FATAL,
};
string Getcurtime() // 得到时间
{
time_t cur = time(nullptr);
struct tm cur_tm; //struct tm时间戳
localtime_r(&cur, &cur_tm);
char nowtime[128];
snprintf(nowtime, sizeof(nowtime), "%4d-%02d-%02d-%02d-%02d-%02d", cur_tm.tm_year+1900, cur_tm.tm_mon+1, cur_tm.tm_mday, cur_tm.tm_hour, cur_tm.tm_min, cur_tm.tm_sec);
return nowtime;
}
// level to str
string Leveltostr(LogLevel level) //自定义转字符串
{
switch (level)
{
case LogLevel::DEBUG:
return "BUG";
case LogLevel::INFO:
return "INFO";
case LogLevel:: WARRING:
return "WARRING";
case LogLevel::ERROR:
return "ERROR";
default :
return "UNKONW";
}
}
class Logger
{
public:
Logger() //默认是显示屏打印
{
_fllush_strategy = make_unique<ConsoleLogStrategy>();
}
void EnableConsoleStrategy() //显示
{
_fllush_strategy = make_unique<ConsoleLogStrategy>();
}
void EnableFileStrategy() //文件
{
_fllush_strategy = make_unique<FileLogStrategy>();
}
class LogMessage
{
public:
LogMessage(LogLevel level, string filename, int line_num, Logger &logger)
: _curtime(Getcurtime()),
_level(level),
_pid(getpid()),
_filename(filename),
_line_num(line_num),
_logger(logger)
{
// 把左半部分拼接起来
stringstream ss;
ss << "[" << _curtime << "]"
<< "[" <<Leveltostr(_level)<<"]" //自定义转字符串,否则无法执行
<<"["<<_pid<<"]"
<<"["<<_filename<<"]"
<<"["<<_line_num<<"]"
<<"-";
_log_string_info+=ss.str();
}
//重载<<,当后面输入时,直接当里面的内容
template<typename T>
LogMessage& operator<<(const T&info)
{
stringstream ss; //输入流充当字符串内容
ss<<info;
_log_string_info+=ss.str(); //拼接
return *this; //返回
}
~LogMessage()
{
if(_logger._fllush_strategy) //析构执行显示操作
{
_logger._fllush_strategy->SyncLog(_log_string_info);
}
}
private:
string _curtime; // 时间
LogLevel _level; // 等级
pthread_t _pid; // 线程pid
string _filename; // 当前文件名字
int _line_num; // 行号
string _log_string_info; // 完整的内容
Logger &_logger;
};
//重载()// 这里故意写成返回临时对象
LogMessage operator()(LogLevel level,string filename,int line_num)//临时对象???
{
return LogMessage(level,filename,line_num,*this);
}
~Logger(){}
private:
unique_ptr<LogStrategy> _fllush_strategy;
};
//全局
Logger logger;
#define LOG(level) logger(level, __FILE__, __LINE__)
#define Enable_Console_Log_Strategy() logger.EnableConsoleStrategy()
#define Enable_File_Log_Strategy() logger.EnableFileStrategy()
}
宏替换:
cpp
//全局
Logger logger;
#define LOG(level) logger(level, __FILE__, __LINE__)
#define Enable_Console_Log_Strategy() logger.EnableConsoleStrategy()
#define Enable_File_Log_Strategy() logger.EnableFileStrategy()
怎么使用 ?
线程池

线程池概念
⼀种线程使⽤模式。**线程过多会带来调度开销,**进⽽影响缓存局部性和整体性能。
⽽线程池维护着多 个线程,等待着监督管理者分配可并发执⾏的任务。这避免了在处理短时间任务时创建与销毁线程的 代价。
线程池不仅能够保证内核的充分利⽤,还能防⽌过分调度。可⽤线程数量应该取决于可⽤的并发处理器、处理器内核、内存、⽹络sockets等的数量。
线程池的应⽤场景:
• 需要⼤量的线程来完成任务,且完成任务的时间⽐较短。⽐如WEB服务器完成⽹⻚请求这样的任 务,使⽤线程池技术是⾮常合适的。因为单个任务⼩,⽽任务数量巨⼤,你可以想象⼀个热⻔⽹站 的点击次数。但对于⻓时间的任务,⽐如⼀个Telnet连接请求,线程池的优点就不明显了。因为 Telnet会话时间⽐线程的创建时间⼤多了
•对性能要求苛刻的应⽤,⽐如要求服务器迅速响应客⼾请求。
• **接受突发性的⼤量请求,但不⾄于使服务器因此产⽣⼤量线程的应⽤。**突发性⼤量客⼾请求,在没 有线程池情况下,将产⽣⼤量线程,虽然理论上⼤部分操作系统线程数⽬最⼤值不是问题,短时间 内产⽣⼤量线程可能使内存到达极限,出现错误.
线程池的种类
a. 创建固定数量线程池,循环从任务队列中获取任务对象,获取到任务对象后,执⾏任务对象中 的任务接
b. 浮动线程池,其他同上 此处,我们选择固定线程个数的线程池。
这里我们实现第一种
线程池代码:
(1)构造函数
cpp
ThreadPool(int tnum = N)
: _tnum(tnum),
_isrunning(false),
_sleep_num(0)
{
//在线程池下创建线程
for (int i = 0; i < _tnum; i++)
{
_threads.emplace_back([this]()
{ HandlerTask(); });
}
}
除了必要的初始化,在构造线程池的时候,我们还要创建对应数量的线程
【this】捕获当前对象,也才可以访问当前对象ThreadPool的成员变量和成员函数
(2)Start函数
cpp
void Start() // 启动线程
{
if (_isrunning)
return; // 已启动不可再启动
_isrunning = true;
for (auto &x : _threads)
{
x.Start();
LOG(LogLevel::INFO) << "start new thread success: " << x.getName();
}
}
已经启动那就不可再次启动了。
调用线程启动函数
(3)Stop函数和HandlerTask函数
cpp
void Stop() // stop方法
{
if (!_isrunning)
return; // 如果已经停止,那么不可再次停止
_isrunning = false; // 唤醒全部休眠线程
WakeUpAll(); //
}
// HandlerTask方法
void HandlerTask()
{
// 1.定义缓冲区,存储当前线程名字
char name[128];
pthread_getname_np(pthread_self(), name, sizeof(name));
// 2.死循环,直到线程池结束
while (true)
{
T t;
{
// 3.自动上锁,保证线程安全
LockGuard lockguard(_mutex);
// 4.核心等待逻辑,没有任务,且线程池正在运行, 线程等待休眠
while (_task_q.empty()&& _isrunning)
{
_sleep_num++;
_cond.Wait(_mutex);
_sleep_num--;
}
// 5.任务池为空并且线程池结束,停止退出
if (!_isrunning && _task_q.empty())
{
LOG(LogLevel::INFO) << name << " 退出了, 线程池退出&&任务队列为空";
break;
}
// 6.线程休眠停止,一定有任务, 对任务进行处理
t = _task_q.front();
_task_q.pop();
}
t(); // 处理任务,需要在临界区内部处理吗?不需要,一旦把任务处理了,任务已经是线程私有的了!!
}
}
cpp
void WakeUpAll() // 唤醒全部休眠线程
{
LockGuard lockguard(_mutex);
// 休眠数量不为空,那就唤醒所有线程
if (_sleep_num)
{
_cond.Broadcast(); // 唤醒全部前程
}
LOG(LogLevel::INFO) << "唤醒所有的休眠线程";
}

当我们的线程池退出_isrunning == false
内部线程状态?
1.等待:等待状态
2.等待->唤醒:唤醒状态
3.处理任务:执行t()任务
线程退出条件:线程池退出的时候,内部的任务,应该被完全取完&&_isrunning(false)
"我们才可以让对应的线程退出"
否则,就不可以放线程池退出
解释:(1)对于任务状态,随它执行即可
(2)对于等待和等待唤醒状态则:
首先,要把他们全部唤醒WakeUpAll()操作,使得其退出while()请求分配任务的状态,然后统一执行退出,线程终止
问题:为什么要把所有线程都唤醒?
处于休眠的线程无法被stop.无法被检测到标志位。
有任务处理完,没有的通过上述stop
但是在休眠的线程,不可以被直接stop,
所以,stop方法要唤醒所有线程。
唤醒后,检测到队列为空,又继续休眠,怎么办?
体哦阿健加上,队列为空,+_isrunning =true
(4)Join方法:
cpp
void Join()
{
//if(!_isrunning)return;
for(auto& x:_threads)
{
x.Join();
}
}
如果是先进行stop,把_isrunning设置为false,再uu里面对_isrunning进行判断,就会形成一个bug.
Join无法继续进行。
(5)Enqueue方法:
cpp
void WakeUpOne()
{
_cond.Signal();
LOG(LogLevel::INFO) << "唤醒一个的休眠线程";
}
cpp
bool Enqueue(const T &in) // 入队列
{
if (_isrunning)//线程stop了,那还想入队列,没门
{
LockGuard lockguard(_mutex);
_task_q.push(in);
if (_threads.size() == _sleep_num)//全部休眠了就唤醒一个,不然的话就由醒着的线程来执行
WakeUpOne();
return true;
}
return false;
}
如果当前队列不在运行状态,那就不可以入队列
处于运行状态,上锁,推入任务队列,如果全部都休眠了那就唤醒一个,否则就有已经醒着的线程执行
完整代码
cpp
#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <queue>
#include "Log.hpp"
#include "Thread.hpp"
#include "Cond.hpp"
#include "Mutex.hpp"
namespace ThreadPoolModule
{
using namespace std;
using namespace MutexModule;
using namespace CondModule;
using namespace LogModule;
using namespace ThreadModlue;
static const int N = 5;
template <typename T> // 设置模板,这玩意要紧挨着
class ThreadPool
{
private:
void WakeUpAll() // 唤醒全部休眠线程
{
LockGuard lockguard(_mutex);
if (_sleep_num)
{
_cond.Broadcast(); // 唤醒全部前程
}
LOG(LogLevel::INFO) << "唤醒所有的休眠线程";
}
void WakeUpOne()
{
_cond.Signal();
LOG(LogLevel::INFO) << "唤醒一个的休眠线程";
}
public:
ThreadPool(int tnum = N)
: _tnum(tnum),
_isrunning(false),
_sleep_num(0)
{
在线程池下创建线程
for (int i = 0; i < _tnum; i++)
{
_threads.emplace_back([this]()
{ HandlerTask(); });
}
}
void Start() // 启动线程
{
if (_isrunning)
return; // 已启动不可再启动
_isrunning = true;
for (auto &x : _threads)
{
x.Start();
LOG(LogLevel::INFO) << "start new thread success: " << x.getName();
}
}
void Stop() // stop方法
{
if (!_isrunning)
return; // 如果已经停止,那么不可再次停止
_isrunning = false; // 唤醒全部休眠线程
WakeUpAll(); //
}
// HandlerTask方法
void HandlerTask()
{
char name[128];
pthread_getname_np(pthread_self(), name, sizeof(name));
while (true)
{
T t;
{
LockGuard lockguard(_mutex);
while (_task_q.empty()&& _isrunning)
{
_sleep_num++;
_cond.Wait(_mutex);
_sleep_num--;
}
if (!_isrunning && _task_q.empty()) // 线程退出的时候,内部任务被完全取完,并且_isrunning为false的时候,才可以完全停止
{
LOG(LogLevel::INFO) << name << " 退出了, 线程池退出&&任务队列为空";
break;
}
// 一定有任务
t = _task_q.front(); // 从q中获取任务,任务已经是线程私有的了!!!
_task_q.pop();
}
t(); // 处理任务,需要在临界区内部处理吗?1 0
}
}
void Join()
{
for (auto &x : _threads)
{
x.Join();
}
}
bool Enqueue(const T &in) // 入队列
{
if (_isrunning) // 线程stop了,那还想入队列,没门
{
LockGuard lockguard(_mutex);
_task_q.push(in);
if (_threads.size() == _sleep_num) // 全部休眠了就唤醒一个,不然的话就由醒着的线程来执行
WakeUpOne();
return true;
}
return false;
}
~ThreadPool()
{
}
private:
vector<Thread> _threads; // 线程池里面的线程
int _tnum; // 线程池里面的线程个数
queue<T> _task_q; // 任务列表
bool _isrunning; // 运行状态
int _sleep_num; // 休眠者个数
Mutex _mutex; // 锁
Cond _cond; // 条件变量
};
}
线程安全的单例模式
什么是单例模式
是一种常用的软件设计模式,**用于确保一个类只有一个实例,**并提供一个全局访问点来获取这个实例。这种模式在多线程环境中尤其有用,因为它可以确保资源的唯一性和线程安全。
单例模式的特点
某些类,只应该具有⼀个对象(实例),就称之为单例. 例如⼀个男⼈只能有⼀个媳妇. 在很多服务器开发场景中,经常需要让服务器加载很多的数据(上百G)到内存中.此时往往要⽤⼀个单例 的类来管理这些数据.
两种方式实现:饿汉模式和懒汉模式
饿汉实现方式:
吃完饭,立即洗碗
懒汉实现模式:
吃完饭,等下一次吃饭再洗碗
饿汉⽅式实现单例模式:
cpp
template <typename T>
class Singleton {
static T data;
public:
static T* GetInstance() {
return &data;
}
};
只要通过Singleton这个包装类来使⽤T对象,则⼀个进程中只有⼀个T对象的实例.
静态成员属于类,不属于对象。
static 加载到内存,成员讲究意境创建了,不需要我们再次创建。
利用static静态加载的原则。
懒汉⽅式实现单例模式:
cpp
template <typename T>
class Singleton {
static T* inst;
public:
static T* GetInstance() {
if (inst == NULL) {
inst = new T();
}
return inst;
}
};
存在⼀个严重的问题,线程不安全.
第⼀次调⽤GetInstance的时候,如果两个线程同时调⽤,可能会创建出两份T对象的实例.
但是后续再次调⽤,就没有问题了.
懒汉模式实现线程池保护代码:
cpp
#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <queue>
#include "Log.hpp"
#include "Thread.hpp"
#include "Cond.hpp"
#include "Mutex.hpp"
namespace ThreadPoolModule
{
using namespace std;
using namespace MutexModule;
using namespace CondModule;
using namespace LogModule;
using namespace ThreadModlue;
static const int N = 5;
template <typename T> // 设置模板,这玩意要紧挨着
class ThreadPool
{
private:
void WakeUpAll() // 唤醒全部休眠线程
{
LockGuard lockguard(_mutex);
if (_sleep_num)
{
_cond.Broadcast(); // 唤醒全部前程
}
LOG(LogLevel::INFO) << "唤醒所有的休眠线程";
}
void WakeUpOne()
{
_cond.Signal();
LOG(LogLevel::INFO) << "唤醒一个的休眠线程";
}
//懒汉模式,(1)把构造函数和Start函数私有化
ThreadPool(int tnum = N)
: _tnum(tnum),
_isrunning(false),
_sleep_num(0)
{
//在线程池下创建线程
for (int i = 0; i < _tnum; i++)
{
_threads.emplace_back([this]()
{ HandlerTask(); });
}
}
void Start() // 启动线程
{
if (_isrunning)
return; // 已启动不可再启动
_isrunning = true;
for (auto &x : _threads)
{
x.Start();
LOG(LogLevel::INFO) << "start new thread success: " << x.getName();
}
}
public:
//1.第一种:常规(第二种:单例-》懒汉模式)
// ThreadPool(int tnum = N)
// : _tnum(tnum),
// _isrunning(false),
// _sleep_num(0)
// {
// 在线程池下创建线程
// for (int i = 0; i < _tnum; i++)
// {
// _threads.emplace_back([this]()
// { HandlerTask(); });
// }
// }
// void Start() // 启动线程
// {
// if (_isrunning)
// return; // 已启动不可再启动
// _isrunning = true;
// for (auto &x : _threads)
// {
// x.Start();
// LOG(LogLevel::INFO) << "start new thread success: " << x.getName();
// }
// }
//懒汉模式-》设置Getinstance
static ThreadPool<T>* GetInstance()
{
if(inc == nullptr)//第一面墙
{
LockGuard lockguard(_lock);
if(inc == nullptr)
{
inc = new ThreadPool<T>();
inc->Start();
}
}
return inc;
}
void Stop() // stop方法
{
if (!_isrunning)
return; // 如果已经停止,那么不可再次停止
_isrunning = false; // 唤醒全部休眠线程
WakeUpAll(); //
}
// HandlerTask方法
void HandlerTask()
{
char name[128];
pthread_getname_np(pthread_self(), name, sizeof(name));
while (true)
{
T t;
{
LockGuard lockguard(_mutex);
while (_task_q.empty()&& _isrunning)
{
_sleep_num++;
_cond.Wait(_mutex);
_sleep_num--;
}
if (!_isrunning && _task_q.empty()) // 线程退出的时候,内部任务被完全取完,并且_isrunning为false的时候,才可以完全停止
{
LOG(LogLevel::INFO) << name << " 退出了, 线程池退出&&任务队列为空";
break;
}
// 一定有任务
t = _task_q.front(); // 从q中获取任务,任务已经是线程私有的了!!!
_task_q.pop();
}
t(); // 处理任务,需要在临界区内部处理吗?1 0
}
}
void Join()
{
for (auto &x : _threads)
{
x.Join();
}
}
bool Enqueue(const T &in) // 入队列
{
if (_isrunning) // 线程stop了,那还想入队列,没门
{
LockGuard lockguard(_mutex);
_task_q.push(in);
if (_threads.size() == _sleep_num) // 全部休眠了就唤醒一个,不然的话就由醒着的线程来执行
WakeUpOne();
return true;
}
return false;
}
~ThreadPool()
{
}
private:
vector<Thread> _threads; // 线程池里面的线程
int _tnum; // 线程池里面的线程个数
queue<T> _task_q; // 任务列表
bool _isrunning; // 运行状态
int _sleep_num; // 休眠者个数
Mutex _mutex; // 锁
Cond _cond; // 条件变量
//懒汉模式
static ThreadPool<T>* inc;
static Mutex _lock;
};
//懒汉模式初始化
template<typename T>
ThreadPool<T>* ThreadPool<T>::inc = nullptr;
template<typename T>
Mutex ThreadPool<T>::_lock;
}
注意:
1.进程ui核心的思想就延迟加载。需要时才创建。
变量设置为指针的形式。
cppstatic ThreadPool<T>* inc; static Mutex _lock;类内对象必须有对象,但懒汉模式把构造私有化,不允许创建对象,所以成员变量设置为static
2.不允许,单例进行构造和拷贝
cppThreadPool(const ThreadPool<T>&) = delete;//不要拷贝 ThreadPool &operator=(const ThreadPool<T>&) = delete;//不压迫赋值3.初始化
cpptemplate<typename T> ThreadPool<T>* ThreadPool<T>::inc = nullptr; template<typename T> Mutex ThreadPool<T>::_lock;4.私有化构造和Start
cppprivate: //懒汉模式,(1)把构造函数和Start函数私有化 ThreadPool(int tnum = N) : _tnum(tnum), _isrunning(false), _sleep_num(0) { //在线程池下创建线程 for (int i = 0; i < _tnum; i++) { _threads.emplace_back([this]() { HandlerTask(); }); } } void Start() // 启动线程 { if (_isrunning) return; // 已启动不可再启动 _isrunning = true; for (auto &x : _threads) { x.Start(); LOG(LogLevel::INFO) << "start new thread success: " << x.getName(); } } ThreadPool(const ThreadPool<T>&) = delete;//不要拷贝 ThreadPool &operator=(const ThreadPool<T>&) = delete;//不压迫赋值5.提供GetInstance接口
cppstatic ThreadPool<T>* GetInstance() { if(inc == nullptr)//第一面墙 { LockGuard lockguard(_lock);//锁 if(inc == nullptr)//第二面墙 { inc = new ThreadPool<T>(); inc->Start(); } } return inc; }(1)保护对象 定义一把锁static,静态成员无法直接访问类内属性
(2)类内的静态方法,无法访问类内属性,只能访问静态属性,所以成员变成inc设置为静态,就是为了可以调用类内的方法
(3)"三重保险":双端判断,一端加锁,
取消第一面墙:每一个进程进来获取锁,还要等,获取锁后判断,是否为空,效率低,很慢
进行双端加锁,双层判断,提高获取效率
线程安全和重⼊问题
概念
**线程安全:**就是多个线程在访问共享资源时,能够正确地执⾏,不会相互⼲扰或破坏彼此的执⾏结 果。⼀般⽽⾔,多个线程并发同⼀段只有局部变量的代码时,不会出现不同的结果。但是对全局变量 或者静态变量进⾏操作,并且没有锁保护的情况下,容易出现该问题。
重⼊:同⼀个函数被不同的执⾏流调⽤,当前⼀个流程还没有执⾏完,就有其他的执⾏流再次进⼊, 我们称之为重⼊ 。⼀个函数在重⼊的情况下,运⾏结果不会出现任何不同或者任何问题,则该函数被 称为可重⼊函数,否则,是不可重⼊函数。 学到现在,其实我们已经能理解重⼊其实可以分为两种情况
• 多线程重⼊函数
• 信号导致⼀个执⾏流重复进⼊函数


死锁
• 死锁是指在⼀组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程所站⽤,不会释放的资源⽽处于的⼀种永久等待状态。
• 为了⽅便表述,假设现在线程A,线程B必须同时持有锁1和锁2,才能进⾏后续资源的访问

死锁四个必要条件
• 互斥条件:⼀个资源每次只能被⼀个执⾏流使⽤
• 请求与保持条件:⼀个执⾏流因请求资源⽽阻塞时,对已获得的资源保持不放
• 不剥夺条件:⼀个执⾏流已获得的资源,在末使⽤完之前,不能强⾏剥夺

• 循环等待条件:若⼲执⾏流之间形成⼀种头尾相接的循环等待资源的关系

STL,智能指针和线程安全
STL中的容器是否是线程安全的?
不是.
原因是,STL的设计初衷是将性能挖掘到极致,⽽⼀旦涉及到加锁保证线程安全,会对性能造成巨⼤的影 响. ⽽且对于不同的容器,加锁⽅式的不同,性能可能也不同(例如hash表的锁表和锁桶). 因此STL默认不是线程安全.如果需要在多线程环境下使⽤,往往需要调⽤者⾃⾏保证线程安全.
智能指针是否是线程安全的?
对于**unique_ptr,由于只是在当前代码块范围内⽣效,**因此不涉及线程安全问题. 对于shared_ptr,多个对象需要共⽤⼀个引⽤计数变量,所以会存在线程安全问题.但是标准库实现的时 候考虑到了这个问题,基于原⼦操作(CAS)的⽅式保证shared_ptr能够⾼效,原⼦的操作引⽤计数.
其他常⻅的各种锁
• 悲观锁:在每次取数据时,总是担⼼数据会被其他线程修改,所以会在取数据前先加锁(读锁, 写锁,⾏锁等),当其他线程想要访问数据时,被阻塞挂起。
• 乐观锁:每次取数据时候,总是乐观的认为数据不会被其他线程修改,因此不上锁。但是在更新 数据前,会判断其他数据在更新前有没有对数据进⾏修改。主要采⽤两种⽅式:版本号机制和 CAS操作。
• CAS操作:当需要更新数据时,判断当前内存值和之前取得的值是否相等。如果相等则⽤新值更 新。若不等则失败,失败则重试,⼀般是⼀个⾃旋的过程,即不断重试。
• ⾃旋锁,读写锁,加餐课详细介绍



