首页 > 技术文章 > 快速上手大型软件项目有妙招:理清进程、线程、队列及组件间通信

ebuybay 2022-02-12 19:12 原文

  前言

  对于刚入职的新员工,总会遇到一个头疼的问题,那就是如何快速上手公司的大型软件项目。要知道,一般公司里的软件项目都经过上十年的迭代,软件功能很多、架构也很复杂。这比阅读一般的开源项目难度更大,虽然很棘手,但也不要慌,还是有套路可循的。

  现代大型软件的基本架构

  不管是开源项目还是公司大型软件,基本组成主要可以分成四部分:

  1 源代码

  没什么可说的,C/C++/JAVA/Python 等各种语言的源代码。

  2 依赖的库文件

  C/C++ 依赖的静态库(.a)和动态库文件(.so),JAVA 依赖的 package 等。

  3 配置文件

  配置文件的格式一般为 json、yml、xml(现在用的比较少了),这些配置文件存在的意义是什么呢?如果能硬编码在代码里,谁也不乐意维护这些杂七杂八的文件。但是,配置文件能使软件更灵活,能适应不同的应用场景、硬件平台、操作系统,还有些用于 LLT 测试等。

  这些配置文件一般都放在不同的目录下,需要搞明白不同的配置文件对哪个代码模块起作用。

  4 构建系统

  现在有的开源项目用 meson(一款基于 Python3 的构建系统),后端采用 ninja(谷歌的一款增量编译器)。而公司里的 C/C++ 项目采用 cmake/make 居多,你要对如何编写 CMakeLists.txt 有一定的了解。

  一般开源项目里还有个文件夹 examples,里面是一些示例代码。而公司的项目里当然少不了 LLT 测试代码等。

  大型软件项目的分析思路

  1 物理部署

  现代大型软件系统一般都在都是云上运行,分布式、有主备,能容灾。而软件运行最小的载体是 VM(虚拟机)上跑的 docker(容器),一个 docker 里可以跑多个进程。云上虚拟资源的管理由 OpenStack 负责,而 K8S 管理着容器的创建、删除、生命周期等事宜。

  2 程序运行开始阶段

  上面零零散散讲了很多,到这里我们才进入正题。

  现代大型软件必然会使用多进程、多线程技术。不同进程的代码阅读方法是一样的,你只要知道不同进程的功能是什么即可。这里,我们来看一个具体的进程。

  以 C/C++ 为例(JAVA 也是如此),程序的入口是 main 函数,也就是一个进程的入口。进入 main 函数执行的初期,一般是一些初始化、部署、加载等操作,比如读配置文件、加载程序运行所需要的 so、加载进程模版、线程模版、队列模板等。

  3 进程、线程

  main 函数所在的线程称之为主线程,随着代码往下执行,相继会创建出许多子线程,这些子线程可以由主线程创建,也可以由其他子线程创建,每个子线程里都是死循环执行某个或者某些任务。你需要走读代码,了解以下几点:

  有哪些类型的子线程(也就是线程模版个数)?不同类型的线程执行什么样的任务(也就是线程功能)?每种类型的子线程创建了多少个?这些子线程的创建顺序及创建时间点?每个子线程挂载了哪些任务,这些任务是什么?以及这些任务的调度方式和调度权重是什么?子线程上的任务有模板吗?比如任务单元之类的

  4 线程间通信、队列

  一个进程中的不同线程一般来说是相互独立的,比如不同的业务线程。但有时候,线程间也是需要通信的,比如控制线程和业务线程,线程间通信有哪几种方式呢?简单一点的有共享内存,稍微复杂一点的就是通过消息队列实现线程间通信的。你需要明确以下几点:

  哪些线程间需要通信?不同的线程在什么情况下需要通信?通信的目的又是什么?有哪些队列类型(也就是队列模板个数)?不同线程使用的队列个数有几个?线程从哪个队列里读消息?线程往哪个队列里写消息?

  一般来说,某个线程会往其他线程的队列里发消息,而从自己独有的线程队列里读消息。

  5 模块代码

  模块代码大体可以分为两大类:控制模块和业务模块。

  而业务模块代码又可以分为许多子模块,也可以看成一个个微服务。不同的业务模块代码有的跑在不同的线程上,有的同时跑在多个线程上,这种情况下对于模块中所使用到的一些全局变量就需要加锁处理。大量微服务的业务代码跑在业务线程上,这需要一个任务调度框架。这个任务调度框架有多种实现方式,有基于状态机的,也可以基于事件路由。什么是事件路由呢?

  我们事先在配置文件中定义好一堆事件,每个事件下面对应一些微服务处理模块。事件执行上有一个默认的顺序,但也可以在得到某个特定的微服务执行结果时,跳转到非默认事件。

  6 走读代码的利器 - 调试工具

  上述列的问题需要我们走读代码才能寻找到答案。而使用 GDB(Linux 系统下 GNU 的调试工具)可以边调试边走读代码,gdb 可以清晰打印出函数调用栈(命令:bt)信息和线程信息(命令:info thread)。这里强烈推荐大家使用如下命令,跟踪某个具体线程上函数调用情况:

  b 函数名(文件名:行号)thread 线程ID

  总结

  不管是 Windows、Linux 还是 Andorid 中的大型软件,上手方法都是大同小异的。从软件的部署、组成、进程、线程、组件、队列及线程间通信的角度入手,你将迅速对整个工程有了全面认识,而那些具体函数是怎么实现的,逐行看代码就行了,也是阅读大型软件程序中最为简单的部分了。

  以上是个人的一些经验分享,希望对你有所帮助。

推荐阅读