admin管理员组

文章数量:1627749

文章目录

  • init
    • background
    • Content
      • 为什么用cpp? cpp的优缺点? cpp未来的发展方向和前景?
        • content
      • cpp应该怎么学? 学之前需要有什么认知?
        • content
      • 以cpp为例介绍下程序怎么从0 到1的? 怎么理解不同语言的特点?
        • content
          • 程序从编写到运行时候过程?
          • 怎么理解各种各样的编程语言,为什么由这么多编程语言? 静态语言和动态语言的区别? C语言有什么问题?
        • reference
      • 与语言相关的计算机体系基础知识有哪些? 为啥有那么多专业名词, 怎么理解?
        • content
          • 进程、 携程, 多线程, 回调函数, 同步异步, 阻塞非阻塞, epoll等怎么理解?
          • 内存的理解?
          • cpu的理解?内存和cpu交互之间的cache理解
          • io设备的理解? 怎么与cpu交互?
      • cpp的理解与实践
      • *** cpp编译与汇编底层
      • **** if 想让代码质量和性能更好 :
        • 1 if want to make code clear(复用性和简洁) and safe && in cpp language
        • 2 if want to make code clear and safe && in design princle
        • 3 if want to make code clear and safe && in design model
      • **** if 想让工程和架构更好 :
      • 语言级组件
        • 网络库
      • 中间件
      • 框架
        • rpc
      • 分布式中间件
      • ** if 需要辅助撰写代码和搭建项目的工具:让开发更好的工具和网站
        • bazel
        • cmake
        • gdb
        • perf
        • 常用的网站
  • workflow
      • 工作中是如何遵守编程规范的?
      • 工作中是如何使用cpp基本语法?
        • background
        • content
        • summary
        • reference
      • 工作中是如何使用cpp重点语法理解与使用?
        • background
        • content
        • summary
        • reference
    • Reference()
    • VersionLog()
    • note()
      • muduo的工作流

init

background

C++这门语言是一个追求底层的语言, 老实说我为什么选择C++就是因为它够底层, 让我能知道底层大致在干什么。 但是在学习的过程很明显存在不具体的问题, 而且C++语言的语法非常多, 是要做减法的。 基于这个背景, 我积累了一下以自己为中心的C++最佳实践和理解。

Content

为什么用cpp? cpp的优缺点? cpp未来的发展方向和前景?

在刚开始用cpp的时候, 其实并不需要理解为什么用cpp, 只需要按照cpp的语法写出自己的业务就行。 但是随着时间的推移, 你会有个疑惑, 为什么大数据和人工智能用Python多, web业务用java等gc加没指针的语言多? 嵌入式用c多, 工业软件和计算机视觉或者高性能中间件用cpp多?带着这个问题, 我们来看一下。

这个问题的回答主要是对比了各种其他语言,明确了C++在编译器优化和底层内存模型和cache的优势上建立了高性能低延时领域的绝对优势。此外cpp兼容了c和以前的很多旧的语法广度大, 不能通过减法专注某个领域; 没有好的包管理工具; 相比较java等为了开发效率进行裁剪过并且有很好的包管理的语言,在开发上有天然的劣势。 好在目前cpp正在努力通过迭代, 提升开发效率,例如安全指针, 各种容器, boost库等, 未来还会接入模块和更好用的api和类型推导(虽然这些会丢失性能, 因为做了层封装)。未来 随着摩尔定律的消失,计算任务越来越复杂, 但是单核cpu一直没啥提升, cpp的应用会越来越多(减少内核态交互,或者计算任务下放到)。

content
topicnotenotenote
为什么用cpp和cpp的优劣++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
适用领域: 关注于高性能或者底层领域, 而不是业务领域, 业务领域有点像用汽车穿过小巷, 浪费时间还效率低。我想各位应该明白的一点就是C++工程师不是业务的, 它是在业务后面的开发。 他可能不会面对太多业务, 适合做那种put, get这种简单接口, 而且对性能有要求的业务,在这里面去追求性能。 因此你要明白这套技术栈和java那套完全不一样。 java那套是目的在于减轻业务负担, 主要做的工作是函数式编程方式的前端要在前端将一个个数据塞到前台的某个组件上, 而后端java业务通过配置化或者通用的架构去数据源取对应的数据, 并做一些业务上的修改后漏出, 在后台要保证性能和安全。 因此选择了gc语言, 屏蔽了内存等不安全的操作, 并且将各种组件都封装到了 一个业务框架中, 让业务能够快速和安全的实现, 减轻开发人员的负担。 并且随着越来越多人用这个框架, 大量资源的投入让框架越来越好用, 这对公司的帮助很大。 java主要聚焦于web端, 而c++主要在于一些变化不多的领域, 而且这些领域非常分开, 因此不同领域有自己的框架。因此你学cpp要先确定自己的领域, 如果是机器人领域或者cad领域, 那么现在这套技术栈可能很多都用不了, 他们有自己的领域知识, 而他们的领域知识更多在于对物理世界的构建。 而本cpp后端的领域知识其实和java的web差不多,但是可能没那么多业务, 因此没有太好用的框架, 很多东西都要自己搞, 但是优点是有自己的领域知识, 而且不是生活中的领域知识,例如购物这种领域知识是java擅长的, 但是游戏里的交互是c++擅长的, 他俩都能搞, 购物这种领域知识交互复杂但是交互单次的过程简单。 游戏领域的知识交互虽然也复杂但是计算也非常多。因此选择了cpp。 因此我们就要大致抛弃那种前端请求,后端从数据库取数据做各种业务的方式, 能接受黑窗口拿到数据,自己去理解, 并且对外接口不复杂的场景。 其实他们做的东西都差不多嘚, 你要是用termial习惯的话, 他更加有效率哈哈, 改动一次界面前端并不用改动, 虽然不容易记忆和使用, 但是他们是核心, 后期你可以给这些逻辑加上自己想要的界面交互逻辑。因为C++在运行的时候是直接编译成二进制机器代码的, 虽然有些东西是要运行中决定, 例如函数的创建, 但是本身运行速度就很快, 不像java和python等语言需要额外一些工具帮你翻译成机器代码。 虽然python那些不用管理内存的语言开发速度很快, 但是内存垃圾回收容易导致突然变慢, 这在游戏等高性能领域是不被允许的, 你卡了别人不卡直接把你杀了, 电商搜索业务员你卡了或者慢了别人就换app搜了, 而下单业务等可以慢一点,因为已经决定好买了, 愿意花这点时间。 如果不那么在乎性能开销,那么 C++ 并不是最好的语言选择(Java、Go、Python 等正是填补了这些领域);或者软件规模不大、无需很多抽象手段来管理软件的复杂度,那么 C 语言就足够。但如果性能是软件的关键指标,同时又有越来越大的复杂度,那么 C++ 几乎是独一无二的选择。我们看到 C++ 这些年来的发展,都是紧扣 40 年前 Bjarne 为 C++ 设定的“生态位”与“核心精神”而开展的。之前在写这本书之前也说过, cpp的业务模型不多, 主要还是聚焦于底层, 因此要多学习一些计算机的原理,因为没有太多好用的轮子。
C++ 语言优势与认知: 栈上无cache miss是最重要的C++的特点 :控制硬件, 控制生命周期, zero抽象。 支持三种编程方式, 并且一直会改进。 不用自己额外开线程做垃圾回收, 大部分简单变量可以搞成栈上的数据,相比较堆数据汇编语句更少, 可以在编译期花时间进行优化提升运行效率。 C++比java快除了内存模型,更重要的是C++的自己管理数据创建,java是堆上,这是不连续的,cpu的cache频繁无法命中,这是最关键的。
c++的优劣点优点与通用行业领域知识绑定多,没35岁危机, 入门难, 壁垒高。 缺点就是生态太少了, 如果做业务效率太低, 各个C++领域生殖隔离。
1 行业壁垒 : C++很难, 容易形成技术壁垒。 因为没有方便的库, 新手和很多浮躁的人不会进来, 而现在年轻人进来少, 因此值得学习。C++ 语言本身一直在发展, 而且他有右值引用等等极其优化性能的设计, 不像python java等只有官方规定的规则, 不能极致优化。 C++的性能是go的5倍, 在并发、网络、模板等方面具有先天优势。 但是如果是界面开发或者客户端等不是高性能的地方, C++并不占优势。学到后期要去改源码, 裁剪源码, 形成自己的知识体系。 而且不同领域的C++开发认识的C++完全不一样, 写嵌入式的C++程序员对C++理解难以提升。 写视觉和音视频的话很多也都是c with class。 很多时候你会发现没有统一的库, 反而是好事, 将开发能力给了开发人员。 越是底层人员有占比能力的领域, 越不容易被资本收割, 例如嵌入式和互联网。
2 语言本身第三方库比较乱,感觉啥都做不了。 *C/C++ 整套的语法不具备“功能完备性”,单纯地使用这门语言本身提供的功能无法创建任何有意义的程序,必须借助操作系统的 API 接口函数来达到相应的功能。 **当然,随着 C++ 语言标准和版本的不断更新升级,这种现状正在改变;而像 Java、Python 这类语言,其自带的 SDK 提供了各种操作系统的功能。举个例子,C/C++ 语言本身不具备网络通信功能,必须使用操作系统提供的网络通信函数(#include<system/socket>如 Socket 系列函数);而对于 Java 来说,其 JDK 自带的 java 和 java.io 等包则提供了完整的网络通信功能。我在读书的时候常常听人说,QQ、360 安全卫士这类软件是用 C/C++ 开发的,但是当我学完整本 C/C++ 教材以后,仍然写不出来一个像样的窗口程序。许多过来人应该都有类似的困惑吧?其原因是一般 C/C++ 的教材不会教你如何使用操作系统 API 函数的内容。**C/C++ 语言需要直接使用操作系统的接口功能,这就造成了 C/C++ 语言繁、难的地方。**如操作内存不当容易引起程序宕机,不同操作系统的 API 接口使用习惯和风格也不一样。接口函数种类繁多,开发者如果想开发跨平台的程序,必须要学习多个平台的接口函数和对应的系统原理。Java 这类语言,很多功能即使操作系统提供了,如果 Java 虚拟机不提供,开发人员也无法使用。
3 C++本身因为历史包袱的原因, 在新的版本中一直在优化之前的东西。 之前看了一个视频, 讲了很多C语言底层的东西, 从这里可以看出来,这种语言根本不适合上层开发,要程序员考虑的东西太多了,一点也不友好, 用这个语言做业务开发太慢了。精力全花在轮子上, 业务一点也没做。
4 java关注易用性, C++关注性能, 易用性和性能难以同时追求最优。
生态差, 导致大佬在新手阶段都去搞java了。
可以从事的岗位和要求的技术栈参照技术认知和https://www.bilibili/video/BV1jR4y1r7on/?spm_id_from=333.337.search-card.all.click,了解下每个技术栈需要掌握啥。
cpp 过去现在和未来的发展趋势98时代C++主要聚焦于类的抽象等。11年代针对各种并发,指针管理,stl库可用性,auto、range、统一初始化等代码简化的扩展,非常有用。14版本就是对11版本的修补,例如模板泛化和lamdba之类的。17版本主要就是对auto,泛化性再一次进行加强了。20是一个版本大更新,增加了模块非常重要,还有一些数据加速计算的库,还有协程的支持等等。23年预计会对语法上再进行简化。参照吴建中的演讲和程硕的演讲,可以知道我们单核的计算能力10年前就到了顶峰了,虽然内核数一直在增,网络也是一直在增,这对于io密集型业务是免费的午餐。但是对于计算圈集型的业务其实非常难,现在的做法就是硬件加速或者下放内核态等等,这个趋势不会变,而语言也会用C++这种封装不多而且优化过的代码。

cpp应该怎么学? 学之前需要有什么认知?

background:在学习cpp的时候, 感觉cpp里面东西很多, 纠结该怎么学? 好像没有一个比较明确的路线, 只能这学一点那学一点。
对于新手,看看菜鸟的教程, 然后理解一些基本语法。 然后工作中用到什么学什么就行了。 看完菜鸟觉得不够的话, 推荐你去看看 https://www.geeksforgeeks/c-plus-plus/?ref=shm。 对于高级玩家, 语言不是限制, cpp也可以实现垃圾回收反射, 只要对你有用, 完全可以移值过来。

summary:明白程序从编写到运行的过程。 明白编程语言是从0101操作cpu到 汇编操作cpu,再到c语言操作cpu, 再到拥有人类社会规律的面向对象去操作, 再到降低开发心智的python和java发展趋势。 再出新语言, 一定是人类语言级别的, 而且不同语言其实都是基于c语言进行了语言级别的裁剪与封装。 其他语言额外有的,cpp也可以实现, 其他语言不支持的(例如指针), cpp就没办法了。 不要以语言维度看知识点, 厉害的大牛是可以把其他语言中好用的东西给实现过来的, 例如cpp支持垃圾回收。

content

这块的学习其实有很多分享, 有高级专家的分享, 还有大学老师说的语法知识学习。 但是对于我来说, 其实最好的除了基本语法和常用复杂语句, 其他的是用到什么学什么, 不然即使你学了,大概率也学不到太深入, 过段时间也忘记了。 前面我们已经说了, cpp因为有历史背景, C++本身支持面向对象、 面向过程, 函数编程, 模板编程, 模板元编程, 因此包含的太多了, 需要我们自己把我好度, 只用自己应该用的, 不要炫技, 要知道最懂中文的是学校的中文老师, 但是他们是为了教书的, 你作为公司的技术人员, 重心是如何用好核心的语法做出自己领域的功能,搞懂全部语言没啥用, 可能适合去学校教新手各种语法。 并且在工作中大部分都是业务, 除了面向对象是基础,如果是开发直接面对用户的普通应用(Application),那么你可以再研究一下“泛型”和“函数式”,就基本可以解决 90% 的开发问题了。 其他问题基本都是领域相关的知识, 游戏和量化虽然都是用cpp, 但是写出来的代码除了基本框架一样, 里面实际干活的业务完全不一样。

以cpp为例介绍下程序怎么从0 到1的? 怎么理解不同语言的特点?

在学习编程语言的时候, 我想大家的学习方式都是差不多的, 对着一个window下的visual studio 输入课本中的一些指令, 并且书本中告诉了这个指令的作用是定义一个变量, 做个判断, 做个循环等等。 然后我们输入这些指令就可以在黑框中看到结果。 换了个语言再次重复这些操作, 基本上就算入门一个语言了。 但是当我们学的多了, 就会忍不住想要了解这些编程语言是怎么实现出来这些效果的? 我们会发现虽然编程语言不一样, 但是很多报错都是类似的。 我们在工作中不同项目也会面临不同编程语言的选择, 这时候该怎么考虑? 还有当我们用cpp的时候, 如果程序报错是因为汇编指令不兼容怎么办, 课本上好像并没有相关资料。 这些场景下, 我们是非常有必要理解不同语言的特性,还有就是程序怎么从0到1的。

content
topicnotenotenote
****++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
从0101操作cpu到 汇编操作cpu,再到c语言操作cpu, 再到拥有人类社会规律的面向对象去操作, 再到降低开发心智的python和java。最开始是机械到电极管, 最后到晶体管, 开始各种门的封装, 然后开始产生alu单元, alu单元中控制寄存器解析内存中的指令, 控制单元读取内存中的指令后在计算单元alu中计算, 然后写回内存(内存是门控的矩阵)。 这个过程最开始内存中代码是通过打孔的汇编 , 但是慢慢发现很多复杂汇编是可以人类语言简化封装的,为了降低心智, 于是c, C++等带封装的语言通过编译器生成二进制代码了。 可以看出编程语言可以更好的通过描述内存和汇编的操作来实现业务, 其中很多通用的模块被搞成了库和函数。可以看到编程语言在一步步的降低开发心智, 最后的目标是英语表述自动翻译成代码(现在python已经类似了)。但是目前硬件的性能瓶颈了, 所以时间和空间现在存在一个平衡, c++和python各占自己的领域。
程序从撰写到运行经历了什么?(参照下面的问题:程序从编写到运行时候过程)首先讲了编译, 然后讲讲内存内存模型,如何加载的。 程序从main函数启动之后各个寄存器如果操作, 进行堆栈调用时候寄存器如何调用(具体到寄存器)。 在调用过程中发生了内存中断需要加内存到新的页表中, 如果发生物理地址数据访问需要产生页表异常去通过页表实际访问地址。 最后注意一下内存碎片, 库函数缓存, 内核态和用户态的优化。
库和源码的编译区别? 顺序是啥?在编译器和标准库确定的条件下,动态库静态库和源码库有什么优缺点? 动态库的二进制兼容性是啥?有什么问题?区别很大,如果你依赖别人的库,要知道别人当时编译出来这个库的编译选项,不然你如果编译参数有问题,可能头文件展开会有问题。此外一般动态库编译运行速度慢,静态库编译的话如果你依赖的各种库链接的静态库因为版本不一致,函数定义不同,那么链接起来有版本问题,还要找源码进行修改。其实还不如源码全部下载到本地编译进来,速度还是非常可以的。·顺序就是先本地源码,然后再去编译库。项目依赖动态库是不可控的,如果别人热加载升级动态库,你可能会不兼容,所以动态库很难测。·静态库发布也不行,如果别人的静态库依赖boost1.36,而你本地是1.40,,就冲突了,你必须用1.36.如果多几种这样的静态库,他说你必须用4.0,另外一个人说3.6,你这就是死循环了, 不像源码你可以修改成统一的。·源码编译才是王道:动态库别人修改你可以不在乎,主动权在自己手里,而且编译选项集体统一。而且也解决了静态库冲突,因为有冲突,你可以自己修改他们的源码实现。如果动态库的代码改了,影响别人的调用了,就不符合二进制兼容,例如你改了函数变为虚函数。有些操作兼容,有些不兼容,具体参照muduo库的书。
程序从编写到运行时候过程?

一: 编译, 代码写完之后, 我们开始进行程序的编译;(感悟, 所有语言转换成可执行文件之后, 都是指令和数据了, 所以可以跨语言调用。)
预处理(预处理如 #include、#define 等预编译指令的展开与解析, 生成 .i 或 .ii 文件)
编译(编译器进行词法分析、语法分析、语义分析、中间代码生成、目标代码生成、优化,生成 .s 文件)
汇编(汇编器把汇编码翻译成机器码,生成 .o 文件)。
链接( 这个过程第一阶段符号表解析 是把各个.o 文件的elf中同样段进行合并, 所有的符号表中und的变量也就是全局变量和其他库的函数都要在这过程中查找在哪, 解析成功之后给所有的符号分配虚拟地址。 最后生成 .out 文件 (注意这些过程根据objdump 查看elf文件) elf文件会包括了所有的信息。 可执行文件.out和.o大部分都相同, 但是可执行文件还有一个program 告诉系统哪些内容加载到内存中。 一般只加载代码段和数据段。

二 : 加载
操作系统在装载应用程序时,主要分为三步:

  1. 操作系统为进程分配虚拟内存(逻辑内存),一个进程是4G, 分为txt , data ,bss(存储为0或者未初始化的全局变量) , heap, shared stack(从上往下增加的, 要删除一个必须把下面的都删除了, 因此没有内存碎片) kernel 。 动态库和mmap会加载到headp到stack之间。
  2. 建立虚拟内存与可执行文件的映射(在内核态的页表, 以4K大小为单位,将虚拟内存地址与可执行文件的偏移建立映射关系。形成一个虚拟地址到物理地址的页表,注意这里不是寻址的页表)
  3. 将CPU的指令寄存器设置为应用程序的入口地址(入口地址在elf文件中),启动运行。

三 : 运行
在完成3步后,程序开始执行。CPU在脉冲的操作下, 将程序指令地址读到PC指令寄存器,然后如果需要数据, cpu把数据通过地址总线读到存储寄存器中, 然后运算单元对数据进行处理, 处理完成继续程序指令寄存器。

四: 运行过程中堆栈调用过程

  1. 栈在内存中是连续的一块空间(向低地址扩展)最大容量是系统预定好的,堆在内存中的空间(向高地址扩展)是不连续的。堆在内存中呈现的方式类似于链表(记录空闲地址空间的链表)。栈里面不仅有压栈出栈的函数调用, 还能分配局部变量。 这里面能讲解很深的~, 一定要注意。堆栈调用非常关键,深刻理解很有用。 这里面我简单说一下石磊老师课程里面的代码, 局部变量不产生地址, 直接根据栈的偏移量计算的。 直接是一个move指令。 调用函数中{ 会存储之前栈的地址,开辟sum函数的栈空间, 保存之前栈的下一步汇编地址。 然后 执行函数的指令, 最后将存储return结果的形参变量内容交给一个寄存器。 右括号负责将这个调用栈回退(pop 得到之前的栈底的地址, 赋值给当前的栈底esp, 并将call function 的下一行指令直接给pc寄存器执行,(一般在这就回退到真正的main函数栈顶)) , 然后将存返回值的寄存器内容给main栈中的ret , 并开始从main栈顶继续执行。 在这过程中可能会反复发生调度算法切换,但是没事,不影响正常的程序调用。
  2. 堆栈的理解和多线程和进程强相关。 要知道我们多线程是共享进程的虚拟空间中除了栈以为的所有的。 当我们代码执行了api去启动多线程, 就会在这调用要调用其他cpu或者cpu轮询执行多线程。 其实线程和进程概念基本类似, 在linux下线程就是进程实现的。在用多线程执行的时候, 一定要注意对全局变量或者共享的变量考虑线程安全。 有些对象在进程或者当前线程中单个语句中执行是没问题的, 但是 一旦你中途开了多线程, 可能就不行了。

五 : 运行过程中虚拟内存如何工作
当我们代码中访问到具体的数据时候,而不是只是申请虚拟内存的时候, 就会发生页表的实际分配。 接下来就让我们看看这里是怎么切换的。

内存管理单元(MMU)管理着虚拟地址空间和物理内存的转换,操作系统为每一个进程维护了一个从虚拟地址到物理地址的映射关系的数据结构,叫页表,存储着程序地址空间到物理内存空间的映射表。页表寻址中可以通过查询页表中的状态位来确定所要访问的页面是否存在于内存中。每当所要访问的页面不在内存时(缓存不命中),会产生一次缺页异常加中断,此时操作系统会根据页表中的外存地址在外存中找到所缺的一页,将其调入内存。如果执行出错那么就直接退出了。在mmu上查找虚拟地址的物理地址时候,如果内存满了等会做缺页置换算法。例如LRU, 总之可以不用做实际的转换, 直接将物理地址返回给cpu。

如果需要实际访问的话第一次没有缓存而且标志位为空的话会产生缺页异常,如果映射整体是三级表 , 就是经历(1)逻辑地址转线性地址, 这样我们拿到了32位的地址, 然后拆分10 ,10 ,12 大小分别找页表,页, 物理地址。还要注意缺页异常是前提, 随后触发中断, 去真正的将外存放到内存中,说明已经做了一次实际映射,这种就不是malloc , 而是实际的访问了。

  • 此外还要注意虽然看汇编或者gdb每个进程里面的地址都是0x40000以上的,或者说两个进程的地址一样, 但是那是虚拟地址, 实际物理地址是不同的, 不同进程虚拟地址映射的内存物理地址不一样。

六 : cpu的调度算法
每个进程的PCB都是存在所有进程共享的内核空间中,操作系统管理进程,也就是在内核空间中管理的,在内核空间中通过链表管理所有进程的PCB,如果有一个进程要被创建,实际上多分配了这么一个4G的虚拟内存,并在共享的内核空间中的双向链表中加入了自己的PCB。PCB(Process Control Block)进程控制块,描述进程的基本信息和运行状态。
还要注意, 在多道程序而且多用户的情况下,组织多个作业或任务时,就要解决处理器的进程调度。 如果CPU调度算法生效了, 需要进行程序寄存器上下文的保存, 之后再去调度其他的进程。常见的方法有先来先服务法等。

七: 优化性能

最后: 在整个阶段我们需要考虑几个优化方面的问题 ,(1)内存碎片的避免, (2)如何进行优化核态和用户态, 库函数在这个过程中做了什么?

关于内存碎片的避免就是采用每天定时启动, 此外还有malloc底层使用了内存池。采用不同的链表绑定不同需求, 如果请求的大小在链表中满足直接返回, 不满足再去重新开辟。

第二个内核态是发生在一些文件读写或者事件响应中的, 内核态拥有最高权限,可以访问所有系统指令;用户态则只能访问一部分指令。一些对硬件操作或者重要的指令只有内核态才能访问到。例如:当读取文件等(read,write),需要进入内核态。 进入的方式就是软中断和硬件中断。 中断是当前程序需要暂停当前的任务去做的事情, 为了区分不同的中断,每个设备有自己的中断号。系统有0-255一共256个中断。系统有一张中断向量表,用于存放256个中断的中断服务程序入口地址。每个入口地址对应一段代码,即中断服务程序。 一般需要保存现场(当前的执行位置和当前状态到两个寄存器上), 模式切换, 找中断表中的函数, 执行函数, 返回恢复状态。

那么内核态和用户态的交互太多是非常影响性能的, 一般我们使用read, mmap.sendfile去调用内核, 但是不同的方法效率不一样,1. 调用read函数读取文件, 需要进入内核态再返回用户态。这里面拷贝过程比较多。2上面那个步骤可以使用mmap去避免一次拷贝。 kafka使用了这个机制做消息持久化, 它开辟了一个磁盘的mmap , 数据直接从用户态映射到磁盘。
3. 0用户态拷贝 是通过sendfile实现的, 就是说内核直接打开这个底层磁盘, 将数据直接通过内核态发送给客户端。

第三个库函数问题: 其实一些库函数在应用层添加了缓存区, 使用库函数调用可以大大减少系统调用的次数, 这个和系统的内核态还不一样。

怎么理解各种各样的编程语言,为什么由这么多编程语言? 静态语言和动态语言的区别? C语言有什么问题?
  • 其实各种各样的编程语言底层像if else, break,这些还是那一套语法树解析成对应的汇编,或者放到虚拟机由虚拟机来实现对应的汇编。这些大家都是一样的。但是不同的是,在不同语言设计过程中,在语法解析上做了很多工作,例如cpp在语法解析的时侯提供了右值等语法,让程序员可以降低消耗内存的去实现一些东西。java在语法中做了很多限制去约束程序员的代码规范,例如不支持多继承,增加了模块,反射和注释等等高级语法其实底层都是要转成一条条汇编,你用cpp完全也能突现的。但是人家java等是放在语言层次实现的,就会有很大的限制和提升。而python这些也是类似如此,各种语言其实都会封装一些各种各种的高级用法,这些用法在一定程度上决定了生态。这会导致喜欢大型业务项目编程的人倾向于java,喜欢追求性能且业务不怎么变化的领域倾向于++,喜欢算法设计和刚入门的新人倾向于语法简单并且封装程度高的python,这些都是法定的,从他们创造出来的那个时候,创始人其实就已经知道了这种语言会在哪些领域占据绝对优势,而其占据的优势会吸引新手,他们中的新手一部分成长为大牛后会首先为这个语言做贡献,形成良性循环,看看python的机器学习包这些。不像java等语言为了实现对象语义,类的存储占用很大,C++可以节省很多,因此速度非常快。-C++不像其他语言基于模块,还是在基于文本解析的方式加载别人的库和代码。

  • C语言是静态的, 弱类型的语言。弱类型是说类型在运行过程中可以转换, 例如char转int, 静态是说需要提前定义。 c语言最大问题就是一个函数不能支持多种类型无修改的介入, 想要增加一种类型要增加一个函数(你可以用宏和void去实现,但是太复杂)。 这对靠近业务的计算机领域开发效率太低了。 因此cpp在上面做了一层c的包装, 牺牲了写性能, 提升了效率。 C++主要解决了c语言的抽象问题, 想要封装细节, 就要理解类型、数据类型、业务函数的三层封装。 模板通过T来封装int bool等数据类型, 另外一个T2封装包装数据类型的各种数据结构, 用封装的函数对象包装各种本身业务独有的操作。 但是注意不是说c不能做对象, 你可以自己实现, 只不过人家语言是在语言级别封装帮你实现了。

  • 静态语言在编译期程序员自己确定好类型, 也就是内存布局。 而动态语言是在运行阶段系统帮你检查你的数据类型是否符合,大部分情况下你不用用语法表示这是什么类型(但是如果项目复杂了, 这是灾难, 因为其实你还是需要知道这是什么类型的。)

reference
  1. 程序员的自我修养 链接、装载与库 pdf
  2. muduo库中关于程序链接的介绍
  3. 计算机的底层秘密中关于编程语言的介绍
  4. 腾讯课堂中施磊老师的课程
  5. 各种高级语言是怎么转换成汇编让电脑执行的? ref高级语言如何转汇编语言的问题?–菜梦的回答一知乎 和高级语言到汇编的过程主要就是将自己的语法解析成语法树,然后合并成为最终的语法表达数,然后对里面的各个节点进行符号表分配相对地址,然后链接库,最后在运行阶段搞成绝对地址。 | |
  6. 这个具体代码注释也可以看看,说了每行代码在内存哪个位置

与语言相关的计算机体系基础知识有哪些? 为啥有那么多专业名词, 怎么理解?

在前面我们已经通过程序启动的流程, 梳理了操作系统中很多的底层工作, 但是那更多在介绍运行, 可能有些运行中用不到全局概念。 这一节要去理解进程、 携程, 多线程, 回调函数, 同步异步, 阻塞非阻塞, epoll。 这些虽然和上面重复, 但是可能更为细节化。 此外还需要理解内存、cpu和io。

这部分内容真的很干货, 而且难以分类, 每个知识点可能和其他知识点相关。 最终总结一下 :
首先通过web网络去理解一下进程, 线程和协程, 以及对io的高效操作, 这里面自然会理解出函数为参数本身就是传递一些代码作为变量过去。
之后就是cpu的构建原理, 自身的流水线机制, 还有就是和内存交互的时候cache miss在多线程中产生的问题。
最后简单的理解一下io设备在linux下是去驱动生成的句柄, 可以通过epoll操作还有就是mmap和dma技术减少性能消耗。

基本上这一节 + 程序运行原理那一节, 就是计算机底层的知识了。其他的就是软件设计和领域知识和devops等工具的使用了。

content
进程、 携程, 多线程, 回调函数, 同步异步, 阻塞非阻塞, epoll等怎么理解?
  1. 进程, 有自己虚拟空间的cpu最小执行单元, 但不是调度单元。
  2. 线程, 最小调度单元, cpu看调用只看线程和自身的机制, 不管你这个线程在哪个进程。
  3. 协程 : 用户态函数, 在函数执行时候可以中途保存上下文退出的,下次进到函数后走上下文后面, 而不是继续调用。 python中函数中间的yeild就是做这个的。而这些携程是开辟在堆上的, 所以就效率很高。
  4. 回调函数 : 要理解回调函数本身就是把一串内存上的函数代码地址传给其他的地方, 让另外一个执行的地方可以调用本来代码中不相关的地址(因此最好设计通用些)。 而其他的地方一般是个网络库通用接口等。同步回调就是你把自己的函数地址传到本线程中的其他函数中,让对方能执行, 这在很多业务解耦中会用到。 而异步回调是任务在其他cpu中执行。
  5. 同步和异步的区别? 阻塞和非阻塞的区别?
  • 同步也可能不是本线程, 例如阻塞的读取文件, 是当前线程阻塞, 进入内核态线程读取磁盘后返回, 但是本质上算一个线程, 因为你当前线程啥也不干就等待结果回来)
  • 要理解接电话适合同步, 但是发邮件就适合异步。 虽然生活中大部分事情是同步合适, 但是有些事情例如发邮件和买东西, 异步绝对高校。 看你的场景。
  • 异步也分为不关心结果的和关心结果的。 很多都关心结果的。 因此要么用类似promise通知, 要么就是任务比较多的开十几个线程并行, 自己主线程在后面等一等也行, 要么你把你后面要执行的任务通过回调函数传递到对面执行。

其实这些技术的理解还是要以应用场景来。 最好的例子就是网络库。

  • 第一版: 最开始是单进程while 循环做 接受请求, 同步去数据库读取数据, 处理业务, 返回结果。 当请求多的时候, 会发现 很多任务都卡去读取数据上, 因为io耗时是cpu的百倍。 这时候cpu只能空等。
  • 第二版: 每次请求过来创建成多进程做 接受请求, 同步去数据库读取数据, 处理业务, 返回结果。 但是数据交互困难, 开辟困难。
  • 第三版: 开辟进程换成线程,来一个处理一个。 但是我们的线程也是有限的,而且线程会有数据死锁和 安全性问题。 一个线程挂了就全没了。 此外每个子线程在被创建后, 都有自己要管理, 太麻烦。
  • 4 version : 使用epoll 同时管理多个句柄, 把句柄管理任务拆出来, 用一个线程去通过epoll监听。 有任务再去通知对应的线程。 这就是著名的事件驱动也就是reactor。 但是这个还有问题, 对应线程频繁被创建。
    1. version: 加入线程池避免创建频繁。 但是还有个问题, 就是随着业务越来越复杂, handle里面也会再起线程或者rpc调用, 这些会让业务变的更复杂的。
  • 6 : 加入模块化的项目设计结构, 业务算子化, 可以配置化。 但是性能还是差一点意思, 遇到高耗时的任务, 或者为了性能搞很多回调, 套个两三层就会很绝望。
  • 7 : 使用协程或者说M:N的库 : 这样可以写的更同步些, 虽然你感觉自己的任务太串行, 但是遇到阻塞的任务, 函数会切换, 当前线程会去执行其他不在阻塞状态的函数。 所以效率会很高。 这时候还有就是devops和基础组件的问题了,属于自己领域的问题。和计算机基础无关了。
  • 8 :
内存的理解?
  • 这里首先要理解指针是存放的对象的地址, 而不是拷贝内容。 理解了这个之后还需要知道引用更安全些, 因为它不能改地址, 只能改地址中的内容。 因此很多语言为了安全性都弄掉指针了, 有引用就行。 此外还需要理解程序中分配的堆内存一般是通过内存池分配的, 例如tcmalloc等。 这样的化可以避免频繁的让标准库的api去进入内核态调用系统接口, 这种耗时还是挺多的。

  • 常见的内存问题就是未初始化会拿到其他内存池刚刚释放的变量地址, 可能里面有数据, 还有就是地址无效和地址越界了, 越界读还好, 如果越界写是很危险的。

cpu的理解?内存和cpu交互之间的cache理解
  • 首先要知道cpu是通过与或非电路搭建的, 而寄存器是也是一种与或非电路, 不过这个电路的设计有点巧妙, 能够存一些状态在电路上。基于这些电路去设计通用的接口,也就是cpu指令单元, 从而让上层软件开发人员做各种软件。 因此你需要知道底层没必要太多, 要抽象。 软件层没必要那么节省性能, 可能人家cpu厂商为这个功能加个电路, 比你优化的要强很多。
  • 此外要知道cpu空闲的时候是有一个空闲进程负责执行一些关闭各种耗能动作的空闲进程, 当有定时中断来了会去检查中断, 跳出去执行任务。
  • 还要知道cpu是流水线执行的, 流水线我们都知道。 好处是效率高, 但是需要让下一条指令跟在后面一步走, 不能掉队。因此最好让cpu 能够预测你的下一步, 不然流水线的branch miss等各种类型的miss会很高。这个你可以用perf去看。
  • 此外cpu目前指令集是分复杂指令的服务端,例如intel等, arm的精简指令集。 精简的话虽然语句变长, 但是流水线会中断少, 因此目前效果很好。 此外intel那些功能多, 而且也慢慢抄了一些精简指令集。 目前还在打架。
  • 还需要知道cpu的pc寄存器是指向执行指令的。 还有进入内核态, 中断等, 会保存上下文然后进行栈调用,执行完再出来。
  • 此外还需要知道cache, 他会将一部分代码提前加载过来。 但是多线程中, 为了维护不同cpu核的cache,如果全局变量更新的话, 会频繁去同步其他线程的cpu cache去保证一致性。反而不如单线程的操作。 因此我们在开发中要避免cache频繁同步问题。
  • 此外我们还需要知道 指令重排, 指令重排是指多线程中, 单线程出现不了。 多线程中如果你是没有锁的代码。 可能其他线程执行全局操作的时候, 写了两个全局变量, 但是由于松散内存屏障, 可能第二个写入的变量先进到了cache, 第一个还没进去。 但是其他线程要根据第一个变量才去做第二个变量的判断。 这时候就会出问题。
io设备的理解? 怎么与cpu交互?

io设备本身就是一个小的电脑,也是有自己的简单cpu或者说控制器, 这里面的控制器最上层就是寄存器。 而操作系统需要开发相应的驱动将这些设备寄存器加载到虚拟内存中。 然后操作虚拟内存中特定位置的地址, 执行想要的io操作,例如显示和键盘和网络。 而这些操作都被封装到了操作系统的api中。

  • 举个例子read读取文件的时候, 就是操作对应外设的寄存器让dma去将内存中开辟的地址填充上磁盘的数据, 完成后中断通知cpu。
  • 此外还有俩高级io应用,一个是epoll等管理多个设备驱动的fd, 可以让程序实现同时操作上万个设备的数据读写。 还有就是mmap去读文件会避免先将文件拷贝进来再操作。 对大文件而且是只读场景非常节省性能。

cpp的理解与实践

background: 在理解的计算机的整体底层之后, 我们还要回归到应用上, 而在应用上有很多难点, 例如回调的使用, function等的设计, 这些设计其实和前面的计算机底层原理非常强相关。 此外还有一些基本功。 这里可以简单的罗列一下, 还是还是希望有实际的代码能够积累出来。 不然一套理解, 一套代码, 精力不够吖, 代码还是最重要的, 而且全部聚焦到一起, 也容易串起来。

主题参照连接备注备注备注
基本语法++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
基本语法参照这个自己写的文档和cpp reference
2 难懂但是重要的语法理解++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
fucntion. bind和lambda和闭包之间的理解 2 为什么要函数对象,lamdba表达式是怎么用的?c++11新特性总结(五) 一function、 bind和lambda表达式 -冲冲的文章fucntion. bind和lambda和闭包之间的理解: lambda就是一个简单的函数, 可能只在语句之间用。 bind就是将一个函数进行bind化, 做到函数可以适配很多参数。 function就是函数指针, 但是在这可以指向很多的函数地址, 这样可以方便调用。1. 主要是很多函数都不能带用户专有的参数吧 一般我们都是将bind的函数对象放到function中,然后统-放到map容器中, 提供调用。根据不同的函数名找到map表中对应的函数对象,然后调用。这在web框架中是经常用到的。.
同步和异步的区别,future、 promise、 async这些的用法的理解Blog: https://www.51cto/article/712890.html多线程就是实现异步的一 个方式。异步是让调用方法的主线程不需要同步等待另-线程的完成,从而可以让主线程干其它的事情。同步异步是你代码等待不等待,是不是线性的。传统的异步编程中多线程传输数据进行通信比较麻烦,需要锁进行同步,但是future、promise. async等现在更为方便了。其实promise底层就是多线程, 不过他们增加和信号通知, 做到能够拿返回值。https://www.51cto/article/712890.html 参照这个, 异步最简单的就是线程, future也是一种, 还有消息队列等参照这个回答, 很棒的, 如何比较好的理解和区分同步、异步、回调、阻塞、非阻塞、线程、进程、协程、并行、并发、事件循环等等术语? - Manjusaka的回答 - 知乎https://www.zhihu/question/266222348/answer/304632928, 主要就是看你怎么做事情
怎么理解复杂指针强转Blog:很多复杂的代码都是拿到指针之后做个复杂类型的强转,要知道指针内部是存储地址还有类型的,一般你用父类指针传过来,会强转成派生指针。所有复杂对象都是成员加函数,只不过有些里面对加锁访问,有些会有自己的子工具类,有些会用一个map表存储函数对象、线程等等东西。本来线程是一个实体的概念,为什么这些东西能被存呢?因为他们本身就是一个地址,调用到他们地址里的系统调用,就会生效。
为什么容器中可以放线程,td, 函数指针这些,然后做一 下非常多的花里胡哨的操作?Blog:我们需要按照inux下一切动作皆文件的思想去考虑这个问题。所有的动作也是操作这些文件调用对应的接口去实现实际的操作的,我们完全可以在容器中奖对应文件或者函数的地址放进来,然后用的时候拿到这个地址进行对应接口的操作。例如你放了线程对象, 然后调用了join函散, 那么线程就会自动执行的。
函数指针的定义需要明白, 因为结构看起来还挺复杂的。Blog:其实就是int(int, int)类型, 然后注意void(*)是接收各种类型函数指针的
C++的指针和引用放右边吧, 不然会歧义。Blog:但是为了规范性, 其实放左边更好看一些。
模板是比较难的, 不做lib的话, 一般业务没有必要自己写, 泛化性太强, 先跳过。Blog:concept是解决模板编译出错非常难看懂的一个语法。
typedef using typename class 区别?Blog:
cpp string的问题: 缺少格式化和分割api接口。Blog:d
nullptr解决NULL被int 接收的问题,专门为指针设计。Blog:d
**如何理解智能指针?Blog:智能指针就是用栈变量利用raii管理堆资源。如果有交叉引用记得weakptr就行。
***如何理解右值引用?右值引用其实本身就是一个左值引用, 不过你搞成右值的话,或者通过move转成右值的话, 传入一个构造函数会调用移动构造函数, 移动构造函数是吸星大法, 会把你的东西转移,原来的就用不了了。
2 非常难懂的知识点++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
内存模型主要是内存模型, 明白多线程中cpu对程序的执行顺序会做修改, c11做了一套内存模型来解决并发访问一致性。 但是这里也有约束强性能差的问题, 需要再学习一下。

为什么cpp编译那么慢?

  • cpp编译这里我主要想说的C++为了兼容c,没有像其他语言使用模块导入,而是使用头文件将文本加载进来用编译器解析一下。但是这样非常麻烦的,可能会递归导入很多文件进来。此外在编译过程中做了很多指令集优化。

*** cpp编译与汇编底层

其实主要目的就是能够脑海中模拟出来各种程序是怎么在电脑上运行的就行。

主题参照连接备注备注备注
**程序编译的流程,动态库 静态库 源代码编译 汇编转cpp的理解 **++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2. 为什么cpp编译那么慢?cpp编译这里我主要想说的C++为了兼容c,没有像其他语言使用模块导入,而是使用头文件将文本加载进来用编译器解析一下。但是这样非常麻烦的,可能会递归导入很多文件进来。此外在编译过程中做了很多指令集优化。
为什么其他语言感觉开源很多?主要就是不用兼容c,可以直接将别人的库编译成元数据,不像cpp会把别人代码加载过来再解析一遍,cpp用别人的库,如果对方不通用化,成本还是很高的。
动态库静态库,源代码编译的理解ref 程硕1. 库和源码的编译区别? 顺序是啥?
区别很大,如果你依赖别人的库,要知道别人当时编译出来这个库的编译选项,不然你如果编译参数有问题,可能头文件展开会有问题。此外一般动态库编译运行速度慢,静态库编译的话如果你依赖的各种库链接的静态库因为版本不一致,函数定义不同,那么链接起来有版本问题,还要找源码进行修改。其实还不如源码全部下载到本地编译进来,速度还是非常可以的。·顺序就是先本地源码,然后再去编译库。

在编译器和标准库确定的条件下,动态库静态库和源码库有什么优缺点?
·项目依赖动态库是不可控的,如果别人热加载升级动态库,你可能会不兼容,所以动态库很难测。·静态库发布也不行,如果别人的静态库依赖boost1.36,而你本地是1.40,,就冲突了,你必须用1.36.如果多几种这样的静态库,他说你必须用4.0,另外一个人说3.6,你这就是死循环了, 不像源码你可以修改成统一的。·源码编译才是王道:动态库别人修改你可以不在乎,主动权在自己手里,而且编译选项集体统一。而且也解决了静态库冲突,因为有冲突,你可以自己修改他们的源码实现。
动态库的二进制兼容性是啥?有什么问题?·如果动态库的代码改了,影响别人的调用了,就不符合二进制兼容,例如你改了函数变为虚函数。有些操作兼容,有些不兼容,具体参照muduo库的书。
计算机组成原理相关知识理解cpu中间有寄存器缓存和运算寄存器等等,然后再访问内存的。请问CPU,内核,寄存器,缓存,RAM,ROM的作用和他们之间的联系?-温戈的回答一知乎https://www.zhihu/question/24565362/answer/2288686350https://blog.csdn/shenmingxueIT/article/details/115524046 硬件的各种交互逻辑
汇编代码和C++互转的理解 ,帮助你理解程序的运行理解cpu中间有寄存器缓存和运算寄存器等等,然后再访问内存的,一些基本的指令要回,例如mov啥的。参照汇编指令—用GDB调试汇编-不雨的文章-知乎https://zhuanlan.zhihu/p/259625135 这个讲的很好了,但是并不是逐条讲的,所以需要看https://mp.weixin.qq/s/qrNjN3v0qe5AkV9TOhCxFw
各种语言底层的理解1. 各种高级语言是怎么转换成汇编让电脑执行的? ref高级语言如何转汇编语言的问题?–菜梦的回答一知乎 和高级语言到汇编的过程主要就是将自己的语法解析成语法树,然后合并成为最终的语法表达数,然后对里面的各个节点进行符号表分配相对地址,然后链接库,最后在运行阶段搞成绝对地址。

**** if 想让代码质量和性能更好 :

1 if want to make code clear(复用性和简洁) and safe && in cpp language
theme传送地址或者参照概述
1 if want to make code clear(复用性和简洁) and safe && in cpp language:++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
在遇到迭代器、模板泛型编程的时候用auto在编译阶段, 其实编译器完全 是能够知道左边表达代表的内容的,可以放心使用, 但是auto不能用来
做接参和返回值(11版本不行)。
在泛型编程中返回值用decltype去推算一个表达式的类型,通过这个类型定义一个变量。Blog:d
typedef可以让过长的复杂数据结构换一个带有业务意义的命名,但是现在人们一般都使用using,因
为可以在模板中使用的.Blog:d
可以使用{}进行统一的初始化, 更为统一 些,底层是实现了一个{}的构造。Blog:d
复杂数据用结构体或者类封装, 不要用简单的单一数据。Blog:d
使用for来遍历容器,Blog:但是别在遍历的时候去修改里面的数据, 会出问题。
过去有很多调用其他的函数的方式,例如复杂的函数指针,但是建议使用function去做,一般用到将一个成员函数作为回调给一个线程池,线程池那边来请求调用。如果一个函数不具备给function的条件,通过bind去绑定 成一个函数地址, 给function。Blog:d
lambda表达式相比较bind更为方便简洁,降低代码复杂程度,推荐函数式编程时候先考虑这种Blog:d
元组tuple类型python中的字典,可以用来代替简单的字段少的结构体,但是不推荐多用。Blog:d
头文件只写声明, 不写实现, 只导入自己编译需要的头文件,设置命名空间避免污染, 加入program once避免重复引用Blog:d
源代码:遵守头文件的包含顺序; 派生类中用using减少父类构造的书写; const & 入口常用 ;对于带值语义的资源对象不能拷贝构造例如socket和文件操作; 使用stl容器或者智能指针(shared和weak)去接管堆数据, 做到出作用域自动析构; 多线程中一读多写用双buff解决安全性;Blog:
dBlog:d
dBlog:d
2 if want to make code clear and safe && in design princle
1 代码设计方面++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
封装 :不能对不该暴露的数据进行暴露。 数据和处理在两个不同的类,导致封装的不彻底,很多操作跑到其他地方去实现了, 属于贫血模式, 正常调用类应该做各种调用, 不做过多的数据处理, 这是接口类中应该做得, 除非无法封装。Blog:d
安全性: 少用全局变量以免破坏封装性, 不管有没有问题都要对入口的参数做检查, 访问下标时候一定要判断保证不越界, 访问对象的属性时候要确保对象不是空的Blog:d
应该将很多业务类中的通用功能用utils类封装一下, 然后各个业务类需要用到的时候调用一下他的static函数。Blog:d
多用组合少用继承。 组合是接口(接口是指一层继承)各种功能类,而不是一个大的抽象实体类。 一般代码量大,而且存在很多继承冲突的时候, 用组合。Blog:d
多用接口抽象: 对于经常变动的代码, 还是需要接口的, 而且接口的函数命名不能太具体,只说做了啥, 不要说怎么做。Blog:d
复杂且类似类可以用C++模板或者宏定义, 这样可以搞成通用接口Blog:参照东哥的cpp代码思想,
2 代码设计原则和经验中++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
**首先要使用Google的编程规范,除了两格的空格不认同,其他都要遵循。 最主要的就是变量命名规范; 空格规范; 函数内用命名注释; 函数以上必须有简单用法注释; 避免有魔鬼数字;不要自己过度优化, 编译器比你聪明;一个空格代表停顿; {}使用少行的那种 ; 三层for循环就要重构 ; 参数入口尽量是可选项, 不能让任意不符合规范的数据进来; 定义重构代码维护, 好的设计不是一开始写出来了,需要重构; 要认真对待每个代码, 参照飞机事故原则; 错误纠错要在编译器阶段做好, 不要运行阶段兜底, **Blog:此外还需要注意很多代码都是一次性的事情, 尤其是工作中的, 一旦完成给别人调用, 就不能轻易的改, 这点要非常注意, 不然吃大亏。
类单一, 单一原则就像做饭一样, 对于大厨来说是一个感觉, 对于新手大致就是一个类200行代码, 10个函数, 当你到了中级自然也会根据感觉来判断了, 好用就是单一。发展的角度看单一, 可能不同业务, 不同时间, 单一的标准不一样, 但是遵旨就是好用。Blog:d
开闭原则保证类似逻辑不要修改之前的代码, 只增加不修改。遵旨就是让经常变化的地方给个不变的抽象接口, 避免直接与细节交互过多。 其中可以参照设计模式中策略模式,责任链, 还有就是函数参数用类而不是多种数据类型。Blog:d
优化的方向就是单一职责类,模块化, 业务和非业务分开, 通用业务或者非业务下沉不依赖高级业务; 此外还有多态抽象继承等。 总之就是上层越抽象但是实现越单一, 复用就越厉害。Blog:d
问题要提前暴露所有问题都是越堆积越难解决的, 有问题要提前想办法解决掉。
核心原则是最小表达力的去解决各种问题参照之前一个文档,关于设计模式等等都是为了解决复杂问题的, 而不是找复杂问题解决的。
3 if want to make code clear and safe && in design model

设计模式要干的事情就是解耦,也就是利用更好的代码结构将一大坨代码拆分成职责更单一的小类,让其满足高内聚低耦合等特性。创建型模式是将创建和使用代码解耦,结构型模式是将不同的功能代码解耦,行为型模式是将不同的行为代码解耦。而解耦的主要目的是应对代码的复杂性。设计模式就是为了解决复杂代码问题而产生的。

****++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
遇到项目中各个模块经常使用一个类的情况用线程安全的单例。Blog:d
遇到成员函数接口类似的类, 而且创建起来非常分散, 可能一次要创建很多个, 一次又需要创建两三个类的时候可以用工厂模式统一创建, 将不同的创建需求用工厂类封装一下。 一般像UI类就具有统一的接口,各种类非常类似, 创建时候就非常适合工厂类, 此外ioc容器也是,创建和释放自己控制。Blog:d
遇到某个类的功能要根据不同的业务分开展开的时候, 就要用到代理模式, 例如权限管理。Blog:d
遇到一些类的接口无法与另外的类的接口交互, 而其他类可以的时候, 需要打补丁去补充。Blog:d
遇到不想影响当前类的功能, 但是需要加一些功能的时候, 就可以用装饰器来做。例如可以用装饰器加日志或者统计一些东西。Blog:d
遇到多种类型频繁访问一个类查询数据的场景,可以设计成异步的发布订阅模式。典型的就是网络io通信中的epoll。Blog:d
当同级别if else 多的时候, 而且是平级的时候,就可以用map表的策略模型。Blog:d
当不同级别的if else 比较多的时候, 可以用状态机, 它相比较策略模式的话能够感知其他状态的存在, 从而搞出来优先级。Blog:d
当流程非常明确, 而且需要不停加各种新的流程在总流程当中的时候就用责任链。Blog:d
当做框架或者配置化内容的时候,想要只将接口暴露给对方的话就用模板方式模式Blog:d
**4 if 想让代码性能好 **++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
解析等操作最好的初始化时候做好。运行时不要做, 浪费时间。Blog:d
多线程中读写锁并不一定快, 最好用队列解决线程问题。Blog:d
&& 和move去将一个大的临时对象传给一个有右值引用的对象。Blog:d
empalce右值和reverse都应该更先使用。Blog:d
不要炫技操作, 影响编译器的优化能力。Blog:d
不见得unorderedmap比vector查找速度快, 要看存储的数据类型。ref https://blog.csdn/acaiwlj/article/details/49781877d

**** if 想让工程和架构更好 :

theme传送地址或者参照概述
1 想让工程和架构更好++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1. 单元测试有维护成本, 单测关注输入和输出就好。参照这个自己写的文档和cpp reference
合理的框架没有最优解, 根据不同业务会有变动, 但是总的来说, 设计模式的原则还有符合现实中人们的思考方式, 是代码工程合理基本要求。 其他的根据业务变动时间和空间。Blog:d
基本合理的项目框架Blog:目前来说一般项目(非金融游戏等领域,关注于一般工程例如电商,聊天)最好的架构将业务拆分成几个不耦合的单元, 单元内就是配置化的chain链路结构单个功能,每个功能调用自己的功能类,可能功能类还依赖一些子类, 然后顺序执行。 在执行过程中各个模块解耦较大, 不会有回去执行的业务逻辑,模块内设计合理。
*** 后端项目中各种优化ref https://mp.weixin.qq/s/JZtNXkQLm0p9-0mnLT9tLA和https://stibel.icu/md/project/develop/develop-process.html作者说了后端项目的各种优化点,还有怎么设计架构等方案, 感觉值得仔细阅读。

语言级组件

1 内存池组件++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
什么是内存池? 有什么用?Blog: https://blog.csdn/shenmingxueIT/article/details/114639289?spm=1001.2014.3001.5502 还有微信上雨乐的文章, 都讲的非常详细这个很简单, 要知道我们程序如果需要调用系统的资源,例如开辟内存或者申请一个fd,或者往网卡发送数据, 是需要保存当前的堆栈信息后进入内核态的, 这个保存进入内核态是要触发中断的, 显然这种操作是比较耗时的,因此glic里面实现了内存池, 里面预存的一些内存。
内存池 ptmalloc, jmalloc, tcmallocBlog:gc malloc在内存分配上其实性能不高, 现在都是用tcmalloc和ptmalloc。他们底层实现了内存的调整。
实操经验这个一般可以通过环境变量设置缓存释放的时间。 此外有些内存例如unorderedmap可能析构了后并不释放内存给系统, 如果它占用过大是有问题的, 需要你自己调整一下。
2 json组件++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
什么是json, 优点在哪里他就是一个数据的序列化和反序列化的协议, 一般用于网络传输, 你可以将别人从网络传过来的反序列化string解析出json进行操作, 然后自己操作完成后序列化发送出去。ref https://blog.csdn/shenmingxueIT/article/details/108960894?spm=1001.2014.3001.5502
实操经验这个一定要注意末尾的,有些解析版本是不支持的。 使用的话你可以包含一个第三方库, 市面上有很多好用的json解析和序列化库。不同 的解析库速度是不一样的, 这取决于数据种类。
**1 protobuf **++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
protobuf这个就是google出的一种序列化协议, 和json和xml的不同, 它是经过压缩的二进制,基本上是json这种的速度一半。 此外pb对rpc有天然支持的类方便你去写自己的rpc, 当然你完全可以自己写。ref https://blog.csdn/shenmingxueIT/article/details/114670839
实战经验一般都是pb字段里面存json, 而整体协议用pb发送。
第三方工具库boost和stlBlog:定时器,数字转字符串,文件读写,线程池等
线程池Blog:就是个通用的生产消费者模型,也就是线程池,这个模型用模板接受各种任务,各种任务的存放通过加锁和条件变量进行控制,然后使用一个线程池往这里面添加各种task.最后调用的时候用多线程去往里面写函数对象, 里面的线程自然会 -直调用。这里面用了很多高级的用法,cpp这一 套就是比较难, 这一 套都是大家抄来抄去的,不是自己创造的。因此学会用, 能看懂就行。(2)这个线程池有个问题就是任务长时间执行,让耗时任务用单独线程执行, 而且任务不能中途挂着等待太多时间, 要有超时时间中断。(3)同步是指任务放到队列中是同步的,需要加锁。 异步是指线程处理是异步的, 不用你放完就等。 但是你的任务应该注意生命周期别过了, 不然里面的函数对象释放了。 (不过你用值Lambd应该没啥问题。)
IOC容器Blog:过去我们对于多种类型数据的依赖可以通过工厂模式来解耦来降低复杂度。但是其实本质还是依赖了工厂 。可以将所有的类以来的外来数据通过统一 IOC容器来管理,这个容器通过配置来设置对象和依赖数据的关系,然后你通过IOC容器去创建这个对象, IOC通过配 去创建你的依赖对象。
AOP切面Blog:- + aop就是将一些日志和权限等和类相关, 但是通过继承表达不好实现, 就用切面取做这些公共行为。 C++这个东西有两种实现,- 种是像java通过编译器级别加入语法进行控制,这样可以进行配置化,还有 种是动态代理设计模式或者模板实现的。 都能用。ref c++11工程实践
并行计算库Blog:d
dBlog:d
dBlog:d
日志库 log4cppBlog:d
xml库Blog:d
配置库
插件库
业务工作流库
监控上报库
网络库
1 网络库++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
什么叫网络库呢?网络库其实算一个比较重的代码组件, 甚至一些出名的库例如moduo可以说是算一个开源框架了, 因为它除了做网络的数据接收与处理, 还有日志模块, 还有json解析模块, 线程池,消息队列,epoll分发等等, 你完全可以基于它来做一些业务了, 但是网络库其实本身可能做业务还是轻了一些, 但是其实按照资格也够了。 如果把他弄得完善一下, 例如有配置等等模块, 其实完全可以算得上是框架了。但是因为我们这次介绍的最小的网络库, 不谈额外的实现, 可能就先放到这里了。
以moduo库为例, 介绍一下网络库的基本知识有哪些?首先你要知道阻塞非阻塞, 异步同步这几个之间的关系。 然后你需要知道epoll使用的是同步非阻塞的方式, 然后你还需要知道reactor和Proactor的区别, 一个是注册完任务自己转发数据到线程中去处理, 另外一个是注册完之后给个callback, 让内核自己调用(proactor我没见过,所以具体 代码怎么实现还不知道)ref 万字长文梳理Muduo库核心代码及优秀编程细节思想剖析 - 地铁站里吃闸机的文章 - 知乎https://zhuanlan.zhihu/p/495016351 和神明博客https://blog.csdn/shenmingxueIT/article/details/113806437?spm=1001.2014.3001.5502 和 自己的笔记https://leopeng.blog.csdn/article/details/118252102
muduo的基本架构参照如下图, 基本上就是主线程开始一个loop去接收用的client,然后连接后的fd分发给subreactor, 他们里面epoll监听读写事件, 当有事件了就进行序列的操作并返回。
详细介绍一下怎么流转的吧?这里不方便展示, 见表格末尾的程序流程介绍主要就是服务启动的时候创建一个accept channel放到主loop上, 然后创建多个线程的loop, 当主loop有请求响应事件, 将链接的fd转发到分loop上, 分loop上如果有读写事件,根据读写事件去进行响应。
muduo库中有哪些好用的服务组件日志啊, buff缓存啊, 定时器啊这些都不错, 此外他对各种类的声明周期的控制, cpp11的应用, 对象的抽象接口和交互能力都是自己学习的榜样。
市面上有什么场景会用到muduo库? 难道只有rpc这些么? 你代码中是不是用不到这个库?有什么常用的库么?只有网络fd才能用epoll么?目前我只在rpc框架上见到过这种网络库的使用, 其实boost库中有基于proactor实现的, proactor主要区别在于其epoll的read操作等不是自己处理的,而是操作系统处理的。 需要注意几乎所有的带有文件io操作的东西都能用epoll, 网络只是最常用的一种, 文件操作,定时器等都可以用。现在市面上最常用的是asio, 这个说实话比较难,我现在还没看懂。只知道大致的意思就是说数据接收发送不再由你的业务线程中做了,直接定义好异步时间, 把send变成aysn_send, 让内核执行, 虽然消耗的资源差不多, 但是它是内核资源, 你自己的业务线程资源可以继续处理其他事情。 需要注意目前这种proactor在linux上用的少, 好像因为异步io本身接口发展不太好。

中间件

框架

rpc
**1 rpc库 **++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
为什么rpc是要放到中间件, 而不是语言级库或者说框架中呢?其实也是可以放进去的,但是其实rpc经常不是一个简单的rpc, 他可能内部封装了很多东西, 例如brpc里面有很多调参的东西。Blog:
rpc的底层原理和实现其实本质就是一个pb协议中定义好数据格式和接口,如果有接口的话, 那么服务端继承接口类重载服务类, 调用端重载stub类。 调用的时候, 继承rpcchannel写一个CallMethod, 里面写上自己内部rpc的私定查找协议(每个rpc都不一样)和找ip的方式和发送方式。 然后你就用你的stub.函数去调用你想调用的远程调用。 他就会去调用你的call_method然后来实现想要的效果。
对于服务端, 既然人家发送过来了, 你的网络模块按理说应该能拿到对应的请求, 你为什么能拿到请求呢? 首先就是你的muduo库已经启动了, 并且绑定了连接和发送函数, 如果有数据过来, 先解析里面的rpc协议头, 然后根据协议头从map中找到对应的function, 这个function绑定的是你protobuf中定义的那个方法的string类型和自己虚函数重写的函数地址, 当数据发送过来,找到你后, 你找到pb的那个函数,进行了调用。 注意默认加个callback函数用来封装一个数据的返回。额外说明一下, 这里各个服务怎么找到的, 基本上都是通过zk, zk会封装到内部的调用底层, 如果你不想要zk, 想要自定义。 那么需要自己写一下自己服务要祖册到哪个中间件, 此外还要自己写怎么通过这个中间件拿到ip,这个也是很常见的一种方式。ref https://blog.csdn/shenmingxueIT/article/details/115773482?spm=1001.2014.3001.5502和https://mp.weixin.qq/s/ll4nUVB28KpyTMS93xAckQ?utm_source=wechat_session&utm_medium=social&utm_oi=848820786134413312
grpc的底层原理
brpc的底层原理
**1 后端库 **++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

分布式中间件

**1 后端库 **++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
redis kv数据库Blog:d

** if 需要辅助撰写代码和搭建项目的工具:让开发更好的工具和网站

bazel

等待添加。

cmake

自己网上找吧。

gdb

自己网上找吧, 就是看线程的bt, 然后查看数据类型, 基本上不用多线程切的, 一般一个任务就一个线程。

perf

自己网上找吧, perf获取时间点, 火焰图生成每个语句的占用时间。

常用的网站
** **++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
cpp官方论坛, 有一些学习资料和讨论:Blog:https://cplusplus/
查看api用法Blog:https://en.cppreference/w/
查看汇编Blog:https://godbolt/
查看高级语法的底层c实现Blog:https://cppinsights.io/

workflow

工作中是如何遵守编程规范的?

file name using my_useful_class.cpp;
class or structure name using MyExcitingClass;
class var name : m_local_variable;
normal var name : local_variable;
system var _local_var;
define name or enum name: A_LOCACL_VAR;
常规函数使用大小写混合, 取值和设值函数则要求与变量名匹配;
name can replace note;
使用对其{}, 和python一致。

//cacl_number.h

#include "a.h"

// calc data by CaclNum   class .
// Example:
//    CaclNum current_calc();
//    current_calc.SetNumLeft(10);
//    current_calc.SetNumRight(10);
//    int sum_result = current_calc.SumNumber(10, 20);
class CaclNum  
{
public:
	
	CaclNum() {}
	~CaclNum() {}
	// return sum of two value.
	int SumNumber(const int input_left, const int input_right);
	bool SetNumLeft();
	int GetNumLeft();

private:
	// calc var for class. 
	int m_num_left;
	int m_num_right;
	std::mutex _mutex;
};

add next content in create head file;

/**************************************************************************
Copyright: SenseTime
Author: Peng Liu
Date:2021-01-09
Description:Provide class details  about c++ grammar. 
**************************************************************************/

工作中是如何使用cpp基本语法?

background
content
summary
reference

工作中是如何使用cpp重点语法理解与使用?

background
content
summary
reference

Reference()

  1. c++编程风格结合《Effective C++》和《Effective STL》可以让自己写出高质量的代码。不然有些欠的债是要自己后面去还的。
  2. c++ 11工程实践
  3. 零声学院课程
  4. 施磊老师课程
  5. boost库指南

VersionLog()

  1. 之前撰写了很多关于C++相关的笔记,
  2. 2022年10月2日14:39:27 开始更新之前的笔记, 让其更优风格和规律。
  3. 2022年10月3日23:24:29 将C++高级编程这本书融合进去了, 除了模板没有搞进去。
  4. 整合之前的内容全部组装到一起,现在感觉能用了。2022年11月12日18:51:45
  5. 2023年2月12日19:07:25 重新整合的一下, 结合设计模式和语言设计, cpp功底应该挺好的。下一步继续完善各种库和将这些经验最好程序化脚本化,表格规范化。
  6. 2023年5月3日16:36:27 完成了新的一版本, 添加了新的结构和内容梳理, 后面按照这个继续添加。

note()

muduo的工作流

  1. 首先我们看看各个模块是干嘛的?
  • Channel 中保存了连接fd的句柄和感兴趣的事件, epoll_ctl主要通过这个channl来注册事件和响应事件。
  • Poller/EpollPoller 里面就是epoll的类, 它里面封装了poll能够将有响应的事件注册进来, 然后活跃的事件会放到 ChannelList* activeChannels) const; 中。
  • EventLoop 这里面::loop()函数会调用之前EpollPoller ->loop拿到活跃事件,拿到活跃事件的话会调用他对应之前绑定的事件。
  • Acceptor就是一个绑定在主reactor上的监听fd,有链接的就将事件分发给其他线程的loop。
  • EventLoopThreadPool里面就是你的多个线程loop,他们负责处理读写事件。
  1. 怎么使用muduo的:
  • 首先EventLoop是mainloop,里面绑定了一个epoll去监听连接事件, 然后EchoServer server里面去存放TcpServer server_; 这里面在EchoServer server初始化的时候, 会跟着初始化。
#include "examples/simple/echo/echo.h"

#include "muduo/base/Logging.h"
#include "muduo/net/EventLoop.h"

#include <unistd.h>

// using namespace muduo;
// using namespace muduo::net;
// RFC 862
class EchoServer
{
 public:
  EchoServer(muduo::net::EventLoop* loop,
             const muduo::net::InetAddress& listenAddr);

  void start();  // calls server_.start();

 private:
  void onConnection(const muduo::net::TcpConnectionPtr& conn);

  void onMessage(const muduo::net::TcpConnectionPtr& conn,
                 muduo::net::Buffer* buf,
                 muduo::Timestamp time);

  muduo::net::TcpServer server_;
};

#endif  // MUDUO_EXAMPLES_SIMPLE_ECHO_ECHO_H



int main()
{
  LOG_INFO << "pid = " << getpid();
  muduo::net::EventLoop loop;
  muduo::net::InetAddress listenAddr(2007);
  EchoServer server(&loop, listenAddr);
  server.start();
  loop.loop();
}


2. TcpServer 初始化会做什么呢?  首先就是初始化loop,这个后面讲, 主要的就是acceptor_, 然后还会设置链接callback到TcpServer::newConnection,newConnection里面会随机选取一个subloop将这个连接fd等封装过去。 但是现在连接还没来。 acceptor在初始化的时候, 
```cpp
TcpServer::TcpServer(EventLoop* loop,
                     const InetAddress& listenAddr,
                     const string& nameArg,
                     Option option)
  : loop_(CHECK_NOTNULL(loop)),
    ipPort_(listenAddr.toIpPort()),
    name_(nameArg),
    acceptor_(new Acceptor(loop, listenAddr, option == kReusePort)),
    threadPool_(new EventLoopThreadPool(loop, name_)),
    connectionCallback_(defaultConnectionCallback),
    messageCallback_(defaultMessageCallback),
    nextConnId_(1)
{
  acceptor_->setNewConnectionCallback(
      std::bind(&TcpServer::newConnection, this, _1, _2));
}

void TcpServer::newConnection(int sockfd, const InetAddress& peerAddr)
{
  loop_->assertInLoopThread();
  EventLoop* ioLoop = threadPool_->getNextLoop();
  char buf[64];
  snprintf(buf, sizeof buf, "-%s#%d", ipPort_.c_str(), nextConnId_);
  ++nextConnId_;
  string connName = name_ + buf;

  LOG_INFO << "TcpServer::newConnection [" << name_
           << "] - new connection [" << connName
           << "] from " << peerAddr.toIpPort();
  InetAddress localAddr(sockets::getLocalAddr(sockfd));
  // FIXME poll with zero timeout to double confirm the new connection
  // FIXME use make_shared if necessary
  TcpConnectionPtr conn(new TcpConnection(ioLoop,
                                          connName,
                                          sockfd,
                                          localAddr,
                                          peerAddr));
  connections_[connName] = conn;
  conn->setConnectionCallback(connectionCallback_);
  conn->setMessageCallback(messageCallback_);
  conn->setWriteCompleteCallback(writeCompleteCallback_);
  conn->setCloseCallback(
      std::bind(&TcpServer::removeConnection, this, _1)); // FIXME: unsafe
  ioLoop->runInLoop(std::bind(&TcpConnection::connectEstablished, conn));
}

本文标签: languagePart