1.日志
具备以下几个指标
1.时间戳
2.日志等级
3.日志内容


修正,加上毫秒




测试

filesystem的exists封装stat系统调用

2.线程池
2.1 代码实现



最推荐的还是lambda表达式


线程池内部每个线程的cb是HT,之后线程start的时候执行ThreadRoutine执行回调就是HT
信号量是PV操作,互相释放对方的资源,信号量是P不动了就阻塞,也就是没资源了,但是条件变量就宽泛许多,叫醒时机可以把控
is_running是临界资源,因为多个从线程执行HandlerTask会访问is_running,也会修改is_running,一个变量为多个线程共享,只要有一个是写,就都得加锁,万一线程A写到一半,也不知道写没写,线程B读的就是没意义的数据
_sleep_count也是临界资源,多个从线程执行HandlerTask会访问_sleep_count,也会修改_sleep_count




首先main创建线程池对象,会有5个线程,每个线程都只是线程对象,之后start创建内核意义上的线程,HandlerTask所在lambda表达式是回调函数,每一个线程执行ThreadRoutine,内部执行回调函数,执行handlertask,阻塞在条件变量_cond下,每Push一个任务,唤醒一个线程执行完继续去队尾等
任务队列放任务

奇怪的是,HandlerTask如果加了最后一句包含quit的日志,创建的5个线程,有的就创建之后莫名其妙就quit了,之后执行任务时这几个线程也不见踪影,通过监控可以看到quit的线程已经没了
2.2 单例模式
创建对象的时机
1.加载到内存时创建对象,比如全局变量,int gval=1;
2.进程再运行期间,创建对象,比如动态申请,int* p=new int; --最佳实践
所谓单例模式也就是在系统中只允许存在某个类/结构体一份对象,也就是只允许在加载或运行期间,整体最多创建一个该类对象,一般用于缓存的时候保证只缓存一次
有懒汉和锇汉两种实现方式
懒汉就是,吃了饭不洗碗,等到下一次吃饭再洗
锇汉就是为了下一次开饭能立马吃到饭,于是吃完饭就洗碗
懒汉方式最核心的思想是"延迟加载",优化服务器(程序)的启动速度
等到用的时候加载数据到内存,一般采用懒汉模式,因为启动的时候可能有多个模块,每个模块都要加载就会拖慢启动速度,而等到用的时候挨个加载,把IO就分散了
把特定类构造、赋值、拷贝构造私有化,赋值和拷贝构造删除
下面加锁用于多执行流获取单例,单执行流直接获取即可


锇汉


3.其它
3.1 线程安全和可重入函数
3.1.1 概念
线程安全,侧重点在线程并发执行上
函数可重入/不可重入是函数的特点
在概念上,二者属于不同类别
函数重入,同一个函数被多个执行流调用,一个流程还没执行完,有其它执行流再次进入,为重入;如果运行结果不会出现问题,这就是可重入函数,否则为不可重入
线程安全,多个线程访问共享资源,能够正确执行,不会出现相互干扰或破坏彼此的执行结果,一般情况下,多个线程并发同一段只有局部变量的代码时,不会出现不同的结果;对全局变量/静态变量的操作,如果没有加锁,就很容易出问题
但在操作上,有交集,线程会调用函数
不可重入的函数一般是线程不安全的,可重入的函数由于信号和加锁的影响,有可能(几率极小)是不安全的,比如线程A执行一个函数,函数内部进行加锁,加完锁,来了信号,对信号处理,信号内部加同一把锁,就会自己把自己挂起,所以函数不可重入,但一般不考虑
为什么线程不安全?因为多执行流读写全局变量导致数据二义性,反是涉及到全局/静态变量,一般都是不安全;如果是局部,那就在线程的栈上,不会因为多执行流导致数据二义性
- 重入可以分两类
- 多线程重入函数
信号导致一个执行流重入函数
下面是由于信号导致链表的问题


常见线程不安全的情况
- 不保护共享变量的函数
- 函数状态随着被调用,状态发生变化的函数
- 返回指向静态变量的函数
- 调用线程不安全函数的函数
常见线程安全的情况
- 对全局变量/静态变量只有读取权限,没有写入权限
- 类的接口是原子操作
- 多个线程执行不会导致结果二义性
常见不可重入情况
- 调用malloc/free的函数,堆上开辟的空间使用全局链表维护
- 调用标准IO库函数,标准IO库很多实现以不可重入方式使用全局数据结构
- 可重入函数体内使用静态数据结构
3.1.2 智能指针,STL与线程安全
STL的设计初衷是将性能挖掘到极致,一旦涉及到加锁保证线程安全,对性能的影响巨大,
不同容器,加锁方式不同,性能影响可能也不同(比如hash表的锁表和锁桶)
STL默认不是线程安全,在多线程的应用场景下,需要加锁保证线程安全
对于智能指针,unique_ptr在当前代码范围内有效,不涉及线程安全
shared_ptr,多个对象共用一个引用计数变量,存在线程安全问题,标准库设计的时候考虑到了,基于原子操作(CAS)的方式保证shared_ptr高效,原子操作即引用计数
3.2 死锁
3.2.1 概念
死锁是一组进程中的各个进程均占用不会释放的资源,但申请被对方占用不会释放的资源处于一种永久等待的状态
比如线程A和线程B都要拿到锁1和🔒2,但机缘巧合之下,A拿到锁1,B拿到锁2,但都不释放锁,还要对方的锁,就等着
比如线程池一个线程可以记录得到锁的时间戳,如果检测到长时间持有锁,就强制剥夺
3.2.2 四个必要条件
- 互斥,一个资源只能被一个执行流使用
- 请求与保持,执行流请求的资源拿不到,保持占有的资源不释放
- 不可剥夺,进程占有的资源不能被剥夺
- 循环等待,上面的例子就是A等B释放,B等A释放,这样问题回到了起点
3.2.3 避免死锁
尝试破坏死锁的四个必要条件
- 破坏互斥,比如上面线程池,用一个队列,可以给每个线程分配一个队列,本来在任务队列这个资源上各个线程是互斥的,但现在不是
- 破坏请求与保持,任何一个执行流发现拿不到自己要的锁,就释放持有的锁
- 不可剥夺,可以给各个线程设置优先级,比如上面A的优先级比B高,A发现B拿着我要的锁,直接从B上把锁剥夺了给自己用
- 循环等待,循环等待就是资源发的时候一点一点发给多个,那就一次性分配给一个,要么全分给一个,要么就不给,下面代码就是
破坏循环等待条件

其它锁
读写锁

自旋锁
