最近这段时间在处理完实验室论文的事之后,都一直在学习如何去实现一个简易的webserver
。今天来记录下学习到的重点知识。
RAII是什么?
RAII,全称资源获取即初始化(英语:Resource Acquisition Is Initialization),它是在一些面向对象语言中的一种惯用法。RAII源于C++,在Java,C#,D,Ada,Vala和Rust中也有应用。
RAII要求,资源的有效期与持有资源的对象的生命期严格绑定,即由对象的构造函数完成资源的分配(获取),同时由析构函数完成资源的释放。在这种要求下,只要对象能正确地析构,就不会出现资源泄露问题。
由于系统的各种资源都是有限的,我们必须准守一个原则:
- 申请资源
- 使用
- 释放
但是由于代码的复杂性,我们很容易忽略第三步,又由于C++
并没有像JAVA
的GC
机制,又要确保资源能够及时的回收。RAII
的出现就极大的简化了资源管理,并能够保证程序的正确性和代码的简洁性。
线程池是什么?
线程池(英语:thread pool):一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。 例如,线程数一般取cpu数量 + 2
比较合适,线程数过多会导致额外的线程切换开销。
线程池模式一般分为两种:HS/HA半同步/半异步模式、L/F领导者与跟随者模式。
- 半同步/半异步模式又称为生产者消费者模式,是比较常见的实现方式,比较简单。分为同步层、队列层、异步层三层。同步层的主线程处理工作任务并存入工作队列,工作线程从工作队列取出任务进行处理,如果工作队列为空,则取不到任务的工作线程进入挂起状态。由于线程间有数据通信,因此不适于大数据量交换的场合。
- 领导者跟随者模式,在线程池中的线程可处在3种状态之一:领导者leader、追随者follower或工作者processor。任何时刻线程池只有一个领导者线程。事件到达时,领导者线程负责消息分离,并从处于追随者线程中选出一个来当继任领导者,然后将自身设置为工作者状态去处置该事件。处理完毕后工作者线程将自身的状态置为追随者。这一模式实现复杂,但避免了线程间交换任务数据,提高了CPU cache相似性。
线程池的实现代码
在C++中定义一个类时,如果不明确定义拷贝构造函数和拷贝赋值操作符,编译器会自动生成这两个函数。当我们希望禁止拷贝类的实例时,就不能用默认生成的这两个函数。
1 | // noncopyable.h |
1 | // mutexlock.h |
1 | // condition.h |
1 | // threadpool.h |
1 | // threadpool.cpp |
1 | // main.cpp |
所谓虚假唤醒,指的是即便我们没有 signal 相关的条件变量(即没有调用 pthread_cond_signal
),等待(调用了pthread_cond_wait
)的线程也可能被(虚假)唤醒,此时我们必须重新检查对应的标记值(以确认是否发生了(虚假)唤醒),又由于(虚假)唤醒可能会发生多次,所以我们最终需要使用循环来进行标记值检查.