linux线程池

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上把锁剥夺了给自己用
  • 循环等待,循环等待就是资源发的时候一点一点发给多个,那就一次性分配给一个,要么全分给一个,要么就不给,下面代码就是

破坏循环等待条件

其它锁

读写锁

自旋锁

相关推荐
取谖慕12.9 小时前
Prometheus+Alertmanager+node_exporter+grafana高可用
linux·运维·grafana·prometheus
小此方9 小时前
Re:Linux系统篇(四十五)信号篇·三:一文讲透 Linux 信号保存机制:block,pending,handler三张表到底在做什么
linux·运维·服务器
2301_7779983418 小时前
Linux信号机制
linux
一叶龙洲20 小时前
win11与Ubuntu之间同步配置、插件
linux·运维·ubuntu
CHANG_THE_WORLD21 小时前
12.总结:深入理解 Linux I/O 多路复用:select、poll、epoll 全解析
linux·运维·服务器
风曦Kisaki21 小时前
# Linux笔记:操作系统优化与资源管理
linux·运维·服务器·笔记
Mr.HeBoYan1 天前
一次持续三天才出现的丢包故障——深入解析 DPDK Memory Ordering、rte_ring 与 CPU Memory Barrier (下)
linux·网络·算法·架构·dpdk
Discipline~Hai1 天前
ARM01-ARM体系架构
linux·c语言·arm开发·架构
RisunJan1 天前
Linux命令-screen(终端复用器)
linux·运维