1.EasyLogger源码学习笔记(1)
2.Vue Router 源码学习笔记5 - 视图更新的源码实现
3.EasyLogger源码学习笔记(5)
4.Vuex 4源码学习笔记 - mapState、mapGetters、速查mapActions、笔记mapMutations辅助函数原理(六)
5.PostgreSQL源码学习笔记(6)-查询编译
6.Mobx源码阅读笔记——3. proxy 还是源码defineProperty,劫持对象行为的速查两个方案
EasyLogger源码学习笔记(1)
在编程中,预处理器通过宏定义执行特定的笔记电脑文件源码在哪逻辑。使用`#ifdef`和`#else`可以实现条件编译。源码当`#ifdef _XXXX`中的速查标识符_XXXX被`#define`命令定义时,编译器将执行`#ifdef`后的笔记程序段1,否则执行`#else`后的源码程序段2。`#ifndef _XXXX`则表示如果标识符未被定义,速查则执行程序段1,笔记反之执行程序段2。源码
ANSI C宏提供了多种实用信息,速查如`__DATE__`返回当前日期,笔记`__TIME__`返回当前时间,`__FILE__`包含当前文件名,`__LINE__`包含当前行号。`__STDC__`常量用于判断程序是否遵循ANSI C标准。`__FUNCTION__`宏在预编译时返回所在函数的名称。
宏参数的处理可以通过`#`将参数变为字符串,使用`##`将两个宏参数连接起来。`__VA_ARGS__`是一个可变参数宏,需配合`define`使用,将宏左侧的`..`内容原样复制到右侧。
`#if defined`和`#if !defined`在功能上相似,都用于判断宏是否定义。`#error`指令在编译时生成错误消息并停止编译,用于警告开发者。
`extern`关键字用于引用其他文件中的函数或全局变量。例如`extern ElogErrCode elog_port_init(void);`声明了一个名为`elog_port_init`的外部函数,调用时需要指明返回值类型和参数。
在多线程编程中,使用`sched_param`结构来管理线程调度参数。`sem_t`表示信号量,用于实现互斥和同步。`pthread_attr_setschedpolicy(&thread_attr, SCHED_RR);`设置进程调度策略为实时轮转调度。
`SCHED_OTHER`默认分时调度策略,`SCHED_FIFO`采用先进先出策略,而`SCHED_RR`是`SCHED_FIFO`的增强版,提供实时轮转功能。使用`sched_get_priority_max(int policy);`和`sched_get_priority_min(int policy);`函数可以获取线程可设置的最高和最低优先级,其中策略参数即上述三种调度策略的源码跑自瞄宏定义。
`pthread_attr_setschedparam(&thread_attr, &thread_sched_param);`用于设置线程的优先级。通过这些函数,开发者可以精细地控制线程调度,提高程序性能。
Vue Router 源码学习笔记5 - 视图更新的实现
History模块的updateRoute方法主要执行三项关键任务。首先,此方法触发了cb函数,该函数相当于注册监听器,这一过程在VueRouter初始化(src/index.js)阶段完成。其次,更新了内部的_route属性。那么,视图为何会据此更新呢?答案在于响应式属性的机制。
VueRouter的install方法(src/install.js)对全局Vue对象进行了扩展,其中将_route属性定义为响应式属性。这意味着每当_route属性变化,视图就会自动更新。
响应式属性的实现原理基于Object.defineProperty,这是一种JavaScript对象属性的动态绑定机制。通过它,可以为对象属性添加读取和写入操作的监听逻辑,从而实现在属性值改变时触发相应的响应行为。
从设计模式的角度来看,这里采用了发布订阅模式。发布者(即属性值)在变化时发出事件,订阅者(视图)接收到事件后执行相应的更新操作。
进一步探索,可以尝试实现类似Object.defineProperty的功能,以深入理解其工作原理和在Vue中响应式系统中的应用。
EasyLogger源码学习笔记(5)
在EasyLogger源码的学习中,我们了解到日志对象使用了互斥锁以确保同一时刻只有一个线程能进行操作,保证了日志管理的安全性与高效性。
对于异步输出,EasyLogger通过信号量实现了优化。当需要等待执行时,某个线程会被阻塞,以减少CPU的占用。这一特性允许用户单独设置异步输出的日志等级,提高系统的灵活性与可控性。
在文件输出时,使用了信号量集合,其中仅包含一个信号量。在线转盘网页源码这一设计确保了同时只有一个线程能向文件中写入日志,避免了多线程并发写入导致的文件混乱。
日志输出的多样选择体现了EasyLogger的灵活性,无论是输出到文件还是串口,都可以根据需要配置是否采用异步输出,以适应不同的应用场景与性能需求。
此外,sem_post函数用于解锁由semby指定的信号量,执行对特定信号量的解锁操作。而semop函数则用于执行一组预先定义的信号量操作,适用于对多个信号量进行原子性操作。
在信号量集合仅包含一个信号量的情况下,使用sem_post函数进行操作可能直接替代使用semop函数。这一设计简化了信号量管理,提高了代码的可读性和效率。
Vuex 4源码学习笔记 - mapState、mapGetters、mapActions、mapMutations辅助函数原理(六)
在前一章中,我们通过了解Vuex的dispatch功能,逐步探索了Vuex数据流的核心工作机制。通过这一过程,我们对Vuex的整体运行流程有了清晰的把握,为深入理解其细节奠定了基础。本章节,我们将聚焦于Vuex的辅助函数,包括mapState、mapGetters、mapActions、mapMutations以及createNamespacedHelpers,这些函数旨在简化我们的开发流程,使其更符合实际应用需求。
请注意,这些辅助函数在Vue 3的Composition API中不适用,因为它们依赖于组件实例(this),而在Setup阶段,this尚未被创建。因此,它们仅适用于基于选项的Vue 2或Vue 3经典API。
以mapState为例,它允许我们以计算属性的形式访问Vuex中的状态。当组件需要获取多个状态时,PHP手机源码编辑通过mapState生成的计算属性可以显著减少代码冗余。若映射的计算属性名称与state子节点名称相同,只需传入字符串数组。此外,通过对象展开运算符,我们能轻松地在已有计算属性中添加新的映射。
深入代码层面,mapState的核心功能在src/helpers.js文件中得以实现。通过normalizeNamespace函数统一处理命名空间和map数据,然后利用normalizeMap函数将数组或对象格式数据标准化,最终返回一个封装后的函数对象。通过这种方式,mapState有效简化了状态访问的实现。
mapGetters、mapMutations、mapActions遵循相似的模式,通过normalizeNamespace统一输入,然后使用normalizeMap统一数据处理,最后返回对象格式的函数集合,支持对象展开运算符的使用。这些函数简化了获取、执行actions和mutations的过程。
createNamespacedHelpers则是为管理命名空间模块提供便利。通过传入命名空间值,它生成一组组件绑定辅助函数,简化了针对特定命名空间的模块操作。此函数通过bind方法巧妙地将namespace参数绑定到返回的函数集合中,实现了高效、灵活的命名空间管理。
本章节对mapState的实现原理进行了深入分析,并展示了其余辅助函数的相似之处。通过理解这些函数的工作机制,我们能更高效地应用Vuex,优化组件间的交互与状态管理。利用这些工具,开发者能够更专注于业务逻辑的实现,而不是繁琐的状态获取和管理。
在探索更多前端知识的旅程中,让我们一起关注公众号小帅的编程笔记,每天更新精彩内容,与编程社区一同成长。
PostgreSQL源码学习笔记(6)-查询编译
查询模块是精准客户抓取源码数据库与用户进行交互的模块,允许用户使用结构化查询语言(SQL)或其它高级语言在高层次上表达查询任务,并将用户的查询命令转化成数据库上的操作序列并执行。查询处理分为查询编译与查询执行两个阶段:
当PostgreSQL的后台进程Postgres接收到查询命令后,首先传递到查询分析模块,进行词法,语法与语义分析。用户的查询命令,如SELECT,CREATE TABLE等,会被构建为原始解析树,然后交给查询重写模块。查询重写模块根据解析树及参数执行解析分析及规则重写,得到查询树,最后输入计划模块得到计划树。
整个查询编译的函数调用流程包括查询分析、查询重写与计划生成三个阶段。查询分析涉及词法分析、语法分析与语义分析,分别由Lex与Yacc工具完成。词法分析识别输入的SQL命令中的模式,语法分析找出这些模式的组合,形成解析树。出于与用户交互的考虑,语义分析与重写放在另一个函数处理,以避免在输入语句时立即执行事务操作。Lex与Yacc是词法与语法分析工具,分别通过正则表达式解析与语法结构定义,生成用于分析的C语言代码。
查询分析由pg_parse_query函数与pg_analyze_and_rewrite函数完成。pg_parse_query处理词法与语法分析,而语义分析与重写在pg_analyze_and_rewrite函数中进行。语义分析需要访问数据库系统表,以检查命令中的表或字段是否存在,以及聚合函数的适用性。
查询重写核心在于规则系统,存储在pg_rewrite系统表中。规则系统由一系列重写规则组成,包括创建规则、删除规则以及利用规则进行查询重写三个操作。规则系统提供定义、删除规则以及利用规则优化查询的功能。PG中实现多种查询优化策略,包括谓语下滑、WHERE语句合并等,通过动态规划与遗传算法选择代价最小的执行方案。
查询规划的总体过程包括预处理、生成路径和生成计划三个阶段。预处理阶段消除冗余条件、减少递归层数与简化路径生成。提升子链接与子查询是预处理中的关键步骤,通过将子查询提升至与父查询相同的优化等级,提高查询效率。提升子链接与子查询的函数包括pull_up_sublinks与pull_up_subqueries。
在路径生成阶段,优化器检查MIN/MAX聚集函数的存在与索引条件,生成通过索引扫描获得最大值或最小值的路径。表达式预处理由preprocess_expression函数完成,包括目标链表、WHERE语句、HAVING谓语等的处理。HAVING子句的提升或保留取决于是否包含聚集条件。删除冗余信息以优化路径生成。
生成路径的入口函数query_planner负责找到从一组基本表到最终连接表的最高效路径。路径生成算法包括动态规划与遗传算法,分别解决路径选择与状态传递问题。路径生成流程涉及make_one_rel函数,最终生成最优路径并转换为执行计划。
在得到最优路径后,优化器根据路径生成对应的执行计划。创建计划的入口函数create_plan提供顺序扫描、采样扫描、索引扫描与TID扫描等计划生成。整理计划树函数set_plan_references负责最后的细节调整,优化执行器执行效率。代价估算考虑磁盘I/O与CPU时间,根据统计信息与查询条件估计路径代价。
查询编译与规划是数据库性能的关键环节。PostgreSQL通过高效的查询分析、重写与规划,生成最优执行计划,显著提高查询执行效率。动态规划与遗传算法等优化策略的应用,确保了查询处理的高效与灵活性。
Mobx源码阅读笔记——3. proxy 还是defineProperty,劫持对象行为的两个方案
这篇文章将深入分析 MobX 的 observableObject 数据类型的源码,同时探讨使用 Proxy 和 Object.defineProperty 这两种实现方案来劫持对象行为的策略。通过分析,我们能够理解 MobX 在创建 observableObject 时是如何同时采用这两种方案,并在创建时决定使用哪一种。
首先,回顾 observableArray 的实现方式,通过 Proxy 代理数组的行为,转发给 ObservableArrayAdministration 来实现响应式修改的逻辑。同样,我们已经讨论过 observableValue 的实现,通过一个特殊的类 ObservableValue 直接使用其方法,无需代理。
对于 observableObject 的实现机制,其特点在于同时采用了上述两种方案,并且在创建时决定使用哪一种。让我们回到文章中提到的工厂方法,其中根据 options.proxy 的值来决定使用哪一种方案。
在 options.proxy 为 false 的情况下,使用第一条路径来实现 observableObject。这通过直接返回 extendObservable 的结果,其中 extendObservable 是一个工具函数,用于向已存在的目标对象添加 observable 属性。属性映射中的所有键值对都会导致目标上生成新的 observable 属性,并且属性映射中的任意 getters 会被转化为计算属性。
这里首先根据 options 参数选择特定的 decorator,这个过程与之前在第一篇文章中通过 options 参数选择特定的 enhancer 类似。实际上,这里的 decorator 起到了类似的作用,甚至在创建 decorator 这个过程本身也需要通过 enhancer 参数。
至于 decorator 和 enhancer 之间的耦合机制,文章中详细解释了 createDecoratorForEnhancer 和 createPropDecorator 函数,通过这些函数我们能够了解到它们是如何将 decorator 和 enhancer 联系起来的。
接下来,文章深入分析了 decorator 的作用机制,包括它如何决定是否立即执行,以及在不立即执行时如何将创建 prop 的相关信息保存下来。通过 initializeInstance 函数,我们了解了如何解决 # 问题,这涉及到如何正确处理那些在创建时未被立即执行的 prop。
最终,通过为 target 对象创建 ObservableObjectAdministration 管理对象,并通过 $mobx 和 target 属性将它们关联起来,我们完成了 observableObject 的创建。如果传入的 properties 不为空,则使用 extendObservableObjectWithProperties 来初始化。这里的代码逻辑相对简单,主要遍历 properties 中的所有键并调用对应的 decorator。
文章还指出,虽然在第一条路径中,使用 Object.defineProperty 重写了 prop 的 getter 和 setter,但在 MobX 4 及以下版本中,使用 Proxy 来实现 observableObject 的逻辑更为常见。Proxy 特性在 ES6 引入后,提供了更强大的能力来劫持对象的行为,不仅限于 getter 和 setter,还包括对象的其他行为。
最后,文章总结了使用 Proxy 方案的优点,包括能够更全面地劫持对象的行为,而不仅仅是属性的 getter 和 setter。Proxy 方案在实现双向绑定时,能够提供更灵活和强大的功能。同时,文章也提到了两种方案的局限性,尤其是在处理对象属性的可观察性方面,Proxy 方案在某些情况下可能更具优势。
EasyLogger源码学习笔记(4)
setbuf函数用于开启或关闭缓冲机制,关闭时使用setbuf(stdout, NULL);。
在编程中,unlikely(x) 和 likely(x) 函数通过宏定义 __builtin_expect(!!(x), 1) 和 __builtin_expect(!!(x), 0) 实现,用以帮助优化编译器,实现等价于if(a)但更高效的条件判断。
semget()函数用于创建或获取信号量,其原型为 int semget(key_t key, int num_sems, int sem_flags)。它接受一个键值、指定信号量数量及标志位,成功时返回信号标识符,失败时返回-1。
semctl()函数用于设置或获取信号量的值,而semop()函数则用于执行信号量的P操作或V操作。
信号量在共享内存管理中扮演关键角色,内核维护一个名为shmid_ds的数据结构,用于管理共享内存段。
利用fseek()函数,可以设置文件流的位置,通过参数offset和whence来确定查找位置的偏移量。
a+方式打开文本文件,允许读写,若文件不存在则创建,读取从头开始,写入只能追加。
sem_post函数(int sem_post(sem_t *sem);)将信号量值增加1,当线程阻塞在该信号量上时,调用此函数会使一个线程解除阻塞,选择机制由线程调度策略决定。
sem_wait函数(int sem_wait(sem_t * sem);)则将信号量值减去1,但需等待信号量值非零时才开始减法操作。
一种应用方法是利用信号量实现类似于信号传递的功能,某线程在特定条件下执行任务,其他线程通过调用sem_post()使信号量加一,该线程在调用sem_wait()后解除阻塞,继续执行。
easylogging源码学习笔记(6)
`LOG` 是默认日志、CLOG自定义日志、LOG_IF条件日志
特殊日志
LOG_EVERY_N、LOG_AFTER_N、LOG_N_TIMES
for (int i = 1; i <= ; ++i) {
LOG_EVERY_N(2, INFO) << "Logged every second iter";
}// 5 logs written; 2, 4, 6, 7,
for (int i = 1; i <= ; ++i) {
LOG_AFTER_N(2, INFO) << "Log after 2 hits; " << i;
}// 8 logs written; 3, 4, 5, 6, 7, 8, 9,
for (int i = 1; i <= ; ++i) {
LOG_N_TIMES(3, INFO) << "Log only 3 times; " << i;
}// 3 logs writter; 1, 2, 3
条件日志和特殊日志可以搭配使用
* `VLOG_IF(condition, verbose-level)`
* `CVLOG_IF(condition, verbose-level, loggerID)`
* `VLOG_EVERY_N(n, verbose-level)`
* `CVLOG_EVERY_N(n, verbose-level, loggerID)`
* `VLOG_AFTER_N(n, verbose-level)`
* `CVLOG_AFTER_N(n, verbose-level, loggerID)`
* `VLOG_N_TIMES(n, verbose-level)`
* `CVLOG_N_TIMES(n, verbose-level, loggerID)`
日志详细等级判定
if (VLOG_IS_ON(2)) {
// Verbosity level 2 is on for this file
}
性能追踪
* `TIMED_FUNC(obj-name)`
* `TIMED_SCOPE(obj-name, block-name)`
* `TIMED_BLOCK(obj-name, block-name)`
这些宏实际上都是关于el::base::type::PerformanceTrackerPtr,一个指向el::base::PerformanceTracker的指针
#if defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_PERFORMANCE_TRACKING)
PerformanceTracker::PerformanceTracker(const std::string& blockName,
base::TimestampUnit timestampUnit,
const std::string& loggerId,
bool scopedLog, Level level) :
m_blockName(blockName), m_timestampUnit(timestampUnit), m_loggerId(loggerId), m_scopedLog(scopedLog),
m_level(level), m_hasChecked(false), m_lastCheckpointId(std::string()), m_enabled(false) {
#if !defined(ELPP_DISABLE_PERFORMANCE_TRACKING) && ELPP_LOGGING_ENABLED
// We store it locally so that if user happen to change configuration by the end of scope
// or before calling checkpoint, we still depend on state of configuration at time of construction
el::Logger* loggerPtr = ELPP->registeredLoggers()->get(loggerId, false);
m_enabled = loggerPtr != nullptr && loggerPtr->m_typedConfigurations->performanceTracking(m_level);
if (m_enabled) {
base::utils::DateTime::gettimeofday(&m_startTime);
}
#endif // !defined(ELPP_DISABLE_PERFORMANCE_TRACKING) && ELPP_LOGGING_ENABLED
}
在构造函数中获取一个时间,
PerformanceTracker::~PerformanceTracker(void) {
#if !defined(ELPP_DISABLE_PERFORMANCE_TRACKING) && ELPP_LOGGING_ENABLED
if (m_enabled) {
base::threading::ScopedLock scopedLock(lock());
if (m_scopedLog) {
base::utils::DateTime::gettimeofday(&m_endTime);
base::type::string_t formattedTime = getFormattedTimeTaken();
PerformanceTrackingData data(PerformanceTrackingData::DataType::Complete);
data.init(this);
data.m_formattedTimeTaken = formattedTime;
PerformanceTrackingCallback* callback = nullptr;
for (const std::pair& h
: ELPP->m_performanceTrackingCallbacks) {
callback = h.second.get();
if (callback != nullptr && callback->enabled()) {
callback->handle(&data);
}
}
}
}
#endif // !defined(ELPP_DISABLE_PERFORMANCE_TRACKING)
}
在析构函数中获取一个时间,处理时间data,使用PerformanceTrackingCallback类型指针callback,并在callback->handle(&data)中处理输出。
由于定义了ELPP_FEATURE_PERFORMANCE_TRACKING,因此在初始化(INITIALIZE_EASYLOGGINGPP)中实际上是安装了一个base::DefaultPerformanceTrackingCallback。
在PerformanceTracker类的handle函数中,callback是一个PerformanceTrackingCallback类型指针,由于安装的是DefaultPerformanceTrackingCallback对象,因此是一个基类指针指向了派生类对象。处理输出的逻辑在DefaultPerformanceTrackingCallback类的handle函数中。
DefaultPerformanceTrackingCallback类的handle函数首先会将数据成员m_data的指针赋值给函数参数,并创建一个base::type::stringstream_t类型的对象ss用于构建输出内容。根据m_data的dataType,输出不同的信息。在输出时,会使用el::base::Writer类构造并输出内容。
学习编程|Spring源码深度解析 读书笔记 第4章:bean的加载
在Spring框架中,bean的加载过程是一个精细且有序的过程。首先,当需要加载bean时,Spring会尝试通过转换beanName来识别目标对象,可能涉及到别名或FactoryBean的识别。
加载过程分为几步:从缓存查找单例,Spring容器内单例只创建一次,若缓存中无数据,会尝试从singletonFactories寻找。接着是bean的实例化,从缓存获取原始状态后,可能需要进一步处理以符合预期状态。
原型模式的依赖检查是单例模式特有的,用来避免循环依赖问题。然后,如果缓存中无数据,会检查parentBeanFactory,递归加载配置。BeanDefinition会被转换为RootBeanDefinition,合并父类属性,确保依赖的正确初始化。
Spring根据不同的scope策略创建bean,如singleton、prototype等。类型转换是后续步骤,可能将返回的bean转换为所需的类型。FactoryBean的使用提供了灵活的实例化逻辑,用户自定义创建bean的过程。
当bean为FactoryBean时,getBean()方法代理了FactoryBean的getObject(),允许通过不同的方式配置bean。缓存中获取单例时,会执行循环依赖检测和性能优化。最后,通过ObjectFactory实例singletonFactory定义bean的完整加载逻辑,包括回调方法用于处理单例创建前后的状态。
2024-12-22 16:42
2024-12-22 16:26
2024-12-22 15:53
2024-12-22 15:37
2024-12-22 15:12
2024-12-22 14:46