线程模块
线程模块线程的作用 在现代 C++ 网络服务器框架中,线程模块是实现并发处理、任务调度和系统资源充分利用的核心组件。Sylar 框架中的线程模块(Thread 类)封装了 POSIX 的 pthread 接口,简化了线程的创建、命名、同步与生命周期管理。 整体框架 模块解析私有成员变量 m_name :当前子线程的名字,在构造函数中我们需要给每一个线程赋予名字。 m_id :当前子线程的真实内核级线程ID,Linux 下使用 pid_t 唯一标识一个线程。 m_thread :当前子线程的线程标识符,用于表示一个线程对象。 m_cb :当前子线程绑定的回调函数,同样在构造函数中需要给每一个线程赋予它们所需要执行的函数 m_semphore :当前子线程的信号量,用于进行线程之间的同步。 重要成员函数 构造函数 12345678910111213141516Thread::Thread(std::function<void()> cb, const std::string &name) : m_cb(cb) ,...
配置模块
配置模块配置模块的作用 统一管理配置项:通过集中式管理配置项,避免硬编码,便于维护与管理。 支持多种类型配置:包括基础类型(如 int, string)、容器类型(如 vector, map)、以及自定义类型(如 CorsConfig)。 支持 YAML 配置文件解析:便于配置文件的可读性和层级表达。 支持运行时动态更新:变更配置项时触发回调函数,便于系统感知配置变更。 配置项基类 整体架构 解析 ConfigVarBase 是所有配置的基类,它主要有两个成员变量 m_name 和 m_description 。前者便是每个配置都应该有的名称,后者表示对该配置的描述 ConfigVarBase 主要包含三个虚函数:toString(),fromString(),getTypeName(),用于给配置子类进行继承。前两个虚函数 toString()和fromString()主要用于配置子类的序列化和反序列化,而后者的 getTypeName() 用于返回配置子类的类型。 类型转换模板类 lexicalCast...
日志模块
日志模块1️⃣ 日志的作用 在服务器的运行过程中,日志可以记录服务器运行过程中的各种事件,所以日志是非常有作用的。 记录服务器的启动和关闭 记录客户端连接/断开 记录HTTP 请求的接收与响应 记录协程调度执行情况 记录异常或错误信息(如断网、文件打不开等) 当运行过程中当出现异常行为(比如响应延迟、崩溃、死锁等)时,开发者可以通过日志快速定位问题所在: 快速定位问题发生的代码逻辑 分析错误发生的上下文(线程 ID、时间戳、调用路径等) 重现问题流程 在 sylar 服务器框架中,使用了类似 Log4cpp 的结构,支持多级日志输出,支持流式日志风格写日志和格式化风格写日志,支持日志格式自定义,日志级别,多日志分离等等功能 2️⃣ 日志级别 整体框架 sylar 框架中主要使用 logLevel 表示日志级别 日志等级 日志等级主要根据严重程度,从大到小依次排序123456789101112enum Level { FATAL = 0, // 致命情况,系统不可用 ALERT = 100, //...
std::jthread和停止令牌
1️⃣ 添加 std::jthread 的动机std::thread的缺点缺点一 std::thread要求在其生命周期结束时,若表示正在运行的线程,则调用join()(等待线程结束)或detach()(让线程在后台运行)。 若两者都没有调用,析构函数会立即导致异常的程序终止(在某些系统上导致段错误)。 由于这个原因,下面的代码会出现错误(除非不关心程序异常的终止) 1234void foo(){ std::thread t{task, name, val};} 当没有调用join()或detach()就表示正在运行的线程t在析构时,程序会调用std:terminate(),后者调用std::abort()。 缺点二 即使使用join()来等待正在运行的线程结束,任然会有一个严重的问题 123456void foo(){ std::thread t{task, name, val}; ... ...
解谜 extern "C"
1️⃣ 作用以及用途 extern "C" 的主要作用就是为了能够正确实现C++代码调用其他C语言代码。 加上 extern "C" 后,会指示编译器这部分的代码按C语言,而不是C++的方式进行编译。 2️⃣ 底层理由 由于C++支持函数重载,因此编译器编译函数的过程中会将函数的参数类型也加到编译后的代码中,而不仅仅是函数名。 而C语言并不支持函数重载,因此编译C语言代码的函数时不会带上函数的参数类型,一般只包括函数名。 例如函数void fun(int, int)编译后的可能是 _fun_int_int—-不同编译器可能不同,但都采用了相似机制,用函数名和参数类型来命名编译后的函数名; 而C语言没有类似的重载机制,一般是利用函数名来指明编译后的函数名的,对应上面的函数可能会是_fun这样的名字。 因此,如果不加 extern "c",在链接阶段,链接器会从 moduleA 生成的目标文件 moduleA.obj 中找 _fun_int_int 这样的符号,显然这是不可能找到的,因为 fun()...
条款06:绝不在构造和析构过程中调用虚函数
1️⃣ 案例引入 假设你有个class继承体系,用来塑模股市交易如买进、卖出的订单等等。这样的交易一定要经过审计,所以每当创建一个交易对象,在审计日志(audit log)中也需要创建一笔适当记录。 123456789101112131415161718192021222324class Transaction { //所有交易的 base classpublic: Transaction(); virtual void logTransaction() const = 0; //做出一份因类型不同而不同 //的日志记录(log entry)};Transaction::Transaction() // base class构造函数之实现{ ··· logTransaction(); // 最后动作是志记这笔交易};class BuyTransaction: public...
条款07:为多态基类声明成虚析构函数
1️⃣ 问题的引入 有许多种做法可以记录时间,因此,设计一个TimeKeeper base class和一些derived classes作为不同的计时方法: 1234567891011class TimeKeeper{public:TimeKeeper () ;~TimeKeeper () :...};class AtomicClock: public TimeKeeper { ... }; //原子钟class WaterClock: public TimeKeeper { ... }; //水钟class WristWatch: public TimeKeeper { ... }; //腕表 许多客户只想在程序中使用时间,不想操心时间如何计算等细节,这应该怎么办呢 ? 使用factory函数 返回指针指向一个计时对象。Factory函数会 ”返回一个base class指针,指向新生成之derived class对象” : 12TimeKeeper*...
条款04:确定对象被使用前已先被初始化
1️⃣ 问题引入 在不同的语境下,对象是否会被初始化时不一定的 ! int x; 如果 x是全局变量,则会被初始化为 0 ; 如果 x是局部变量,则不会被初始化。 类相关123456class Point{ int x , y;}···Point P; 同理的,p的成员变量有时候被初始化为 0,有时候不会。 为什么要初始化对象 ? 读取未初始化的值会导致不明确的行为。 在某些平台上,仅仅只是读取未初始化的值,就可能让你的程序终止运行。 2️⃣ 最佳处理方式 规则一:永远在使用对象之前就先将它初始化 对于内置类型,必须手工完成 ;对于类而言,初始化的责任就落在构造函数将对象的每一个成员都初始化。 规则二:总是在成员初值列中列出所有的变量,使得记住还有哪些成员变量。 3️⃣ 别混淆 “赋值” 和 “初始化” 案例引入12345678910111213141516171819202122class PhoneNumber {... };class ABEntry //ABEntry = "Address...
链接(link)
1️⃣ 概念 定义 链接(linking)是将各种代码和数据片段收集并组合成一个单一文件的过程,这个文件可被加载(复制)到内存并执行。 执行期 链接可以执行于编译时(compiletime),也就是在源代码被翻译成机器代码时。 链接可以执行于加载时(loadtime),也就是在程序被加载器(loader)加载到内存并执行时 链接可以执行于运行时(runtime),也就是由应用程序来执行。 执行对象 现代计算机,链接是由叫做链接器(linker)的程序自动执行的。 2️⃣ 编译器驱动程序 作用 大多数编译系统提供编译器驱动程序(compilerdriver),它代表用户在需要时调用语言预处理器、编译器、汇编器和链接器。 Linux环境下,我们可以在shell中输入如下命令,调用GCC驱动程序 gcc -o - prog main.c sum.c main.c和sum.c分别为两个不同的源文件。 示例程序 12345678910int sum(int *a, int n);int array[2] = {1, 2};int...
条款05:了解C++默默编写时调用了什么函数
1️⃣ 问题引入 当定义一个 empty class 会发生什么? 编译器会为这个 empty class 自动声明: copy 构造函数 copy assignment 操作符 析构函数 同理,如果没有定义任何构造函数,编译器也会声明一个 default 默认构造函数。 2️⃣ 案例1class Empty {}; 等价于: 1234567class Empty {public: Empty() { ... } Empty(const Empty& rhs) { ... } Empty& operator=(const Empty& rhs) { ... } ~Empty() { ... }}; 3️⃣ 产生时机 只有这些函数被调用的时候,它们才会被编译器创建出来。 123Empty e1; // default构造函数 + 析构函数Empty e2(e1); //...