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

其它锁
读写锁

自旋锁

- 已知如下代码,并在两个线程中同时执行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

- 关于函数的重入于不可重入描述正确的是()
A.线程是安全的则线程中调用的函数一定是可重入函数
B.常见可重入的情况包括:不使用全局变量或静态变量,但是可以调用malloc/free等函数
C.函数可重入只是线程安全的一个要素
D.函数线程安全只是函数可重入的一个要素
C
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();
}
};
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);
}
};
- 下面哪些是死锁发生的必要条件(多选)
A.互斥条件
B.请求和保持
C.不可剥夺
D.循环等待
ABCD
- 下列关于银行家算法的叙述中,正确的是()
A.银行家算法可以预防死锁
B.当系统处于安全状态时, 系统中一定无死锁进程
C.当系统处于不安全状态时, 系统中一定会出现死锁进程
D.银行家算法破坏了死锁必要条件中的“ 请求和保持” 条件
A.避免
C.导致系统不安全的因素有很多
D.那是预防需要做的,这是避免
- 在操作系统中,下列有关死锁的说法正确的是(多选)
A.采用“按序分配”策略可以尽可能的破坏产生死锁的环路等待条件
B.产生死锁的现象是每个进程等待某一个不能得到且不可释放的资源
C.在资源动态分配过程中,防止系统进入安全状态,可避免发生死锁
D.银行家算法是最有代表性的死锁解除算法
AB
- 关于死锁的说法正确的有(多选)
A.竞争可剥夺资源会产生死锁
B.竞争临时资源有可能会产生死锁
C.在发生死锁时,必然存在一个进程—资源的环形链
D.如果进程在一次性申请其所需的全部资源成功后才运行,就不会发生死锁
BCD
- 死锁的处理都有哪些方法(多选)
A.鸵鸟策略
B.预防策略
C.避免策略
D.检测与解除死锁
ABCD
- 线程池都有什么作用(多选)
A.降低资源消耗:通过重用已经创建的线程来降低线程创建和销毁的消耗
B.提高线程的可管理性:线程池可以统一管理、分配、调优和监控
C.程序性能更优:创建的线程越多性能越高
D.降低程序的耦合程度: 提高程序的运行效率
ABD
- 以下线程池的关键参数有哪些(多选)
A.线程池中线程最大数量
B.线程安全的阻塞队列
C.线程池中线程的存活时间
D.线程池中阻塞队列的最大节点数量
ABCD
- 无锁化编程有哪些常见方法(多选)
A.针对计数器,可以使用原子加
B.只有一个生产者和一个消费者,那么就可以做到免锁访问环形缓冲区(Ring Buffer)
C.RCU(Read-Copy-Update),新旧副本切换机制,对于旧副本可以采用延迟释放的做法
D.CAS(Compare-and-Swap),如无锁栈,无锁队列等待
ABCD
- 并发编程中通常会遇到三个问题 原子性问题,可见性问题,有序性问题,java/C/C++中volatile关键字可以保证并发编程中的()
A.原子性, 可见性
B.可见性,有序性
C.原子性,有序性
D.原子性, 可见性,有序性
B
可见性:禁止指令重排序
- 下列操作中,需要执行加锁的操作是(多选)
A.x++;
B.x=y;
C.++x;
D.x=1;
ABC
- 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

1968

被折叠的 条评论
为什么被折叠?



