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

破坏循环等待条件
在这里插入图片描述
其它锁
读写锁
在这里插入图片描述
自旋锁
在这里插入图片描述

  1. 已知如下代码,并在两个线程中同时执行f1和f2,待两个函数都返回后,a的所有可能值是哪些?(多选)
int a = 2, b = 0, c = 0
void f1(){                 
  b = a * 2;            
  a = b;               
}
void f2(){
  c = a + 11;
  a = c;
}

A.4
B.13
C.15
D.26

ABCD
在这里插入图片描述

  1. 关于函数的重入于不可重入描述正确的是()
    A.线程是安全的则线程中调用的函数一定是可重入函数
    B.常见可重入的情况包括:不使用全局变量或静态变量,但是可以调用malloc/free等函数
    C.函数可重入只是线程安全的一个要素
    D.函数线程安全只是函数可重入的一个要素

C

  1. 1114. 按序打印
class Foo {
public:
    std::counting_semaphore<1> sem1{0};
    std::counting_semaphore<1> sem2{0};
    Foo() {
        
    }

    void first(function<void()> printFirst) {
        
        // printFirst() outputs "first". Do not change or remove this line.
        printFirst();
        sem1.release();
    }

    void second(function<void()> printSecond) {
        sem1.acquire();
        // printSecond() outputs "second". Do not change or remove this line.
        printSecond();
        sem2.release();
    }

    void third(function<void()> printThird) {
        sem2.acquire();
        // printThird() outputs "third". Do not change or remove this line.
        printThird();
    }
};
  1. 1117. H2O 生成
class H2O {
public:
    std::counting_semaphore<2> sem_h{2};
    std::counting_semaphore<2> sem_o{0};
    H2O() {
    }

    void hydrogen(function<void()> releaseHydrogen) {
        sem_h.acquire();
        // releaseHydrogen() outputs "H". Do not change or remove this line.
        
        releaseHydrogen();
        sem_o.release();

    }

    void oxygen(function<void()> releaseOxygen) {
        sem_o.acquire();
        sem_o.acquire();
        // releaseOxygen() outputs "O". Do not change or remove this line.
        releaseOxygen();
        sem_h.release(2);
    }
};
  1. 下面哪些是死锁发生的必要条件(多选)
    A.互斥条件
    B.请求和保持
    C.不可剥夺
    D.循环等待

ABCD

  1. 下列关于银行家算法的叙述中,正确的是()
    A.银行家算法可以预防死锁
    B.当系统处于安全状态时, 系统中一定无死锁进程
    C.当系统处于不安全状态时, 系统中一定会出现死锁进程
    D.银行家算法破坏了死锁必要条件中的“ 请求和保持” 条件

A.避免
C.导致系统不安全的因素有很多
D.那是预防需要做的,这是避免

  1. 在操作系统中,下列有关死锁的说法正确的是(多选)
    A.采用“按序分配”策略可以尽可能的破坏产生死锁的环路等待条件
    B.产生死锁的现象是每个进程等待某一个不能得到且不可释放的资源
    C.在资源动态分配过程中,防止系统进入安全状态,可避免发生死锁
    D.银行家算法是最有代表性的死锁解除算法

AB

  1. 关于死锁的说法正确的有(多选)
    A.竞争可剥夺资源会产生死锁
    B.竞争临时资源有可能会产生死锁
    C.在发生死锁时,必然存在一个进程—资源的环形链
    D.如果进程在一次性申请其所需的全部资源成功后才运行,就不会发生死锁

BCD

  1. 死锁的处理都有哪些方法(多选)
    A.鸵鸟策略
    B.预防策略
    C.避免策略
    D.检测与解除死锁

ABCD

  1. 线程池都有什么作用(多选)
    A.降低资源消耗:通过重用已经创建的线程来降低线程创建和销毁的消耗
    B.提高线程的可管理性:线程池可以统一管理、分配、调优和监控
    C.程序性能更优:创建的线程越多性能越高
    D.降低程序的耦合程度: 提高程序的运行效率

ABD

  1. 以下线程池的关键参数有哪些(多选)
    A.线程池中线程最大数量
    B.线程安全的阻塞队列
    C.线程池中线程的存活时间
    D.线程池中阻塞队列的最大节点数量

ABCD

  1. 无锁化编程有哪些常见方法(多选)
    A.针对计数器,可以使用原子加
    B.只有一个生产者和一个消费者,那么就可以做到免锁访问环形缓冲区(Ring Buffer)
    C.RCU(Read-Copy-Update),新旧副本切换机制,对于旧副本可以采用延迟释放的做法
    D.CAS(Compare-and-Swap),如无锁栈,无锁队列等待

ABCD

  1. 并发编程中通常会遇到三个问题 原子性问题,可见性问题,有序性问题,java/C/C++中volatile关键字可以保证并发编程中的()
    A.原子性, 可见性
    B.可见性,有序性
    C.原子性,有序性
    D.原子性, 可见性,有序性

B
可见性:禁止指令重排序

  1. 下列操作中,需要执行加锁的操作是(多选)
    A.x++;
    B.x=y;
    C.++x;
    D.x=1;

ABC

  1. CAS(CompareAndSwap),是用来实现lock-free编程的重要手段之一,多数处理器都支持这一原子操作,其用伪代码描述如下,
template bool CAS(T*addr,T expected,T value)
   {
     if(*addr==expected){
       *addr=value;
       return true;
     }
     return false;
   }
   int count=0;
   void count_atomic_inc(int*addr)
   {
     int oldval=0;
     int newval=0;
     do{
       oldval=*addr;
       newval=______+1;
     }until CAS(_______,________,_________)
   }

请完成下面填空,实现全局计数器的原子递增操作()
A.newval,addr,*oldval, oldval
B.oldval,addr,oldval,newval
C.oldval,addr,oldval,*newval
D.oldval,addr,newval,oldval

B
CAS检测addr指向的数据是否相等,如果相等,设置addr指向的值为value

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值