首页
关于
友链
推荐
肥啾解析
百度一下
肥啾GPT
Search
1
宝塔面板登录 phpMyAdmin 提示服务器和客户端上指示的HTTPS之间不匹配
274 阅读
2
Customer complaints evolve with in-car tech
188 阅读
3
JavaScript解析
153 阅读
4
内连接,左连接,右连接作用及区别
112 阅读
5
所谓关系
109 阅读
默认分类
网游架设
手机游戏
python
PHP
Mysql
VBA
C++
JAVASCRIPT
javascript基础
Oracle
生产管理
计划控制
ERP系统开发
APS排产
MES研究
考勤系统
CPA
财管
实务
经济法
战略
审计
税法
藏书架
古典名著
世界名著
编程秘籍
攻防渗透
经管书籍
大佬传经
风雅读物
考试相关
心情格言
拾玉良言
外文报刊
外刊随选
Facebook
Twitter
China Daily
软考
登录
Search
标签搜索
期刊读物
古文
何瑜明
累计撰写
178
篇文章
累计收到
154
条评论
首页
栏目
默认分类
网游架设
手机游戏
python
PHP
Mysql
VBA
C++
JAVASCRIPT
javascript基础
Oracle
生产管理
计划控制
ERP系统开发
APS排产
MES研究
考勤系统
CPA
财管
实务
经济法
战略
审计
税法
藏书架
古典名著
世界名著
编程秘籍
攻防渗透
经管书籍
大佬传经
风雅读物
考试相关
心情格言
拾玉良言
外文报刊
外刊随选
Facebook
Twitter
China Daily
软考
页面
关于
友链
推荐
肥啾解析
百度一下
肥啾GPT
搜索到
178
篇与
的结果
2026-02-10
开发与运行
模块一:软件开发模型与方法论核心:区分不同模型的特点和适用场景。模块二:需求分析与系统设计需求分析:需求分类:功能需求(做什么)、性能需求(做到什么程度)、非功能需求(安全性、可靠性等)数据需求。分析工具:数据流图(DFD):描述数据在系统中的流动和处理。元素:外部实体、加工、数据存储、数据流。数据字典(DD):定义DFD中所有元素的详细说明。实体-关系图(ER图):用于数据建模。(与数据库设计直接相关)系统设计:阶段:概要设计(架构设计、模块划分)、详细设计(模块内部算法与数据结构)。设计原则:高内聚、低耦合。设计工具:结构图(SC图),用于描述软件的模块结构及调用关系。模块三:软件测试与维护核心:测试的分类、阶段、方法及维护类型。测试阶段(按执行顺序和主体分):测试方法:黑盒测试:不关注内部结构,只检查功能。方法:等价类划分、边界值分析(最常用)、错误推测法。白盒测试:关注内部逻辑结构。方法:逻辑覆盖(语句覆盖、判定覆盖、条件覆盖、路径覆盖等)。系统测试内容非常广泛,主要可以归纳为以下几个核心类别:一、功能性测试这是系统测试的基石,验证软件是否实现了需求规格说明书中规定的所有功能。示例:用户注册、登录功能是否正常?电商系统的下单、支付、退货流程是否完整畅通?搜索功能返回的结果是否准确?所有按钮、链接、表单提交是否按预期工作?二、非功能性测试(质量属性测试)这部分关注软件在“多好”的层面上运行,而非“能不能”运行。它决定了用户体验和系统可持续性。性能测试:负载测试:在正常和高峰预期用户负载下,系统表现如何?(如:1000人同时购物)压力测试:超出极限负载时,系统何时会崩溃?如何崩溃?(如:5000人瞬间抢购)稳定性/耐力测试:长时间(如24小时)运行,系统是否稳定,有无内存泄漏?并发测试:多个用户同时操作同一功能或数据时,系统是否正确处理。可用性测试:用户界面是否直观、易用、美观?导航是否清晰?用户能否在没有帮助的情况下完成任务?是否符合目标用户的操作习惯?兼容性测试:跨浏览器测试:在Chrome, Firefox, Safari, Edge等主流浏览器上是否表现一致?跨平台/设备测试:在Windows, macOS, iOS, Android等不同操作系统或手机、平板、电脑上是否正常?分辨率/屏幕适配测试。安全性测试:是否存在SQL注入、跨站脚本(XSS)等常见漏洞?用户认证和授权机制是否牢固?(如普通用户能否访问管理员页面?)敏感数据(如密码、银行卡号)是否加密传输和存储?可靠性测试:系统在指定条件和时间内,无故障运行的能力如何?发生故障后,能否优雅地恢复或提示?三、其他专项测试安装/卸载测试:软件的安装过程是否顺畅?安装后能否正常运行?卸载是否彻底?文档测试:用户手册、帮助文档、在线提示等内容是否准确,且与软件实际功能一致?备份与恢复测试:系统提供的备份和灾难恢复机制是否有效。本地化/国际化测试:针对不同语言、地区(如日期、货币格式)的版本是否适配正确。特别的,路径测试其实是白盒测试的一种经典技术,属于代码级测试。它关注的是程序内部逻辑路径的覆盖情况,比如判断分支的组合。这和系统测试的黑盒视角有本质区别——系统测试是把软件当整体,不关心内部路径,只关心输入输出是否符合需求。软件维护:改正性维护:修复发现的错误(约20%)。适应性维护:使软件适应变化的环境(硬件、OS等)(约25%)。完善性维护:扩充功能、改善性能(约50%)。预防性维护:为未来改进打基础(约5%)。模块四:项目管理与质量保证软件度量 - McCabe环路复杂度:公式:V(G) = m - n + 2。其中 m 是程序图的有向边数,n 是节点数。意义:衡量程序逻辑复杂性,数值越高越复杂,可测试路径越多。(每年几乎必考1题计算)质量模型:ISO/IEC 9126 软件质量特性:6大特性(功能性、可靠性、易用性、效率、可维护性、可移植性)。过程改进模型:CMM/CMMI能力成熟度模型:5个等级。1-初始级,2-已管理级,3-已定义级,4-量化管理级,5-优化级。重要知识点清单McCabe环路复杂度的计算。四种软件维护类型的定义与识别。各测试阶段的执行者和依据文档(表格对比)。瀑布模型 vs. 敏捷开发的特点对比。黑盒测试 vs. 白盒测试的代表性方法。软件设计原则:高内聚、低耦合。CMMI的5个成熟度等级名称。{card-default label="沟通路径数量" width=""}在进行软件开发时,采用无主程序员的开发小组,成员之间相互平等;而主程序员负责制的开发小组,由一个主程序员和若干成员组成,成员之间没有沟通。在一个由8名开发人员构成的小组中,无主程序员组和主程序员的沟通路径分别是()首先,理解两种模式:无主程序员小组(民主式小组):所有成员之间相互平等,每个成员都可以与其他成员沟通。所以对于n个成员,沟通路径数是组合数C(n,2)= n*(n-1)/2,因为每两人之间有一条沟通路径。主程序员负责制小组:有一个主程序员和若干成员,成员之间没有沟通,只能与主程序员沟通。所以沟通路径是主程序员与每个成员之间的路径,即如果有1个主程序员和m个成员,总人数n = 1+m,那么沟通路径数就是m(因为每个成员与主程序员有一条路径),或者说是n-1。{/card-default}中间件的技术定义和核心作用在计算机系统中,中间件是位于操作系统和具体应用程序之间的软件层。它为不同的应用程序、组件或服务提供通信、协调、管理和数据交换的通用服务。它主要解决以下问题:连接与通信:让运行在不同机器、用不同语言写的程序能互相“说话”。(就像服务员在客人和厨房之间传话)。简化开发:程序员不用每次都从头写网络通信、安全认证、数据格式转换等复杂又通用的代码,直接使用中间件提供的功能就行。(就像餐厅不需要培训每个客人如何与厨师交流,有服务员代劳)。解耦:让前端和后端相互独立。只要中间件的“接口”(菜单和服务规范)不变,厨房换了新厨师(后端系统升级)或APP换了新界面(前端改版),彼此都不受影响。(只要服务员还在,沟通方式不变,后厨和前厅可以各自优化)。提供通用服务:比如消息队列(保证订单不丢失)、事务管理(保证“下单”和“扣款”要么都成功要么都失败)、安全控制、负载均衡(把大量订单合理分配给多个厨师)等。常见的中间件类型(例子)Web服务器/应用服务器:如 Nginx, Apache, Tomcat。它们是浏览器(客户端)和后台程序之间最重要的中间件,负责接收HTTP请求,转发给后端程序,再把结果打包成网页返回。消息队列中间件:如 RabbitMQ, Kafka。像一个可靠的“快递柜”或“传话筒”。A服务把消息存进去就可以干别的事了,B服务有空的时候再来取。保证了系统在高峰时段不会崩溃,服务之间也不会互相等待。数据库中间件:如 MyCat,各种数据库连接池。它管理应用程序和数据库之间的连接,就像“数据库连接的电话总机”,高效分配和复用连接资源。远程过程调用框架:如 gRPC, Dubbo。它让你调用另一个机器上的服务,就像调用自己电脑上的一个函数那么简单,中间所有的网络通信细节都被它隐藏了。API网关:如 Kong, Spring Cloud Gateway。它是所有外部请求进入系统的“总前台”和“安检口”,统一负责鉴权、限流、日志、路由到正确的内部服务。我们需要判断哪个不是中间件技术。先回顾中间件技术的定义:中间件是位于操作系统和应用程序之间的软件,提供通信、协调、数据交换等通用服务,用于连接分布式系统中的不同组件。A) JavaRMI (Java Remote Method Invocation):Java远程方法调用,是一种用于在Java虚拟机之间进行远程通信的机制,允许一个Java程序调用另一个Java虚拟机上的对象方法。它是典型的中间件技术,用于分布式对象通信。B) CORBA (Common Object Request Broker Architecture):公共对象请求代理体系结构,是一种跨语言、跨平台的分布式对象通信标准,由OMG组织制定。它明确是中间件技术,提供了对象请求代理(ORB)作为中间件。C) DCOM (Distributed Component Object Model):分布式组件对象模型,是微软的分布式组件技术,用于在不同机器上的组件间通信。它也是中间件技术。D) JavaApplet:Java小应用程序,是一种嵌入在网页中运行在浏览器端的Java程序,主要用于客户端交互。它运行在客户端浏览器中,不提供分布式通信服务,也不属于中间件。中间件通常位于后台,连接不同组件,而Applet是前端客户端技术。{card-default label="计划与进度工具图" width=""}在项目管理中,甘特图和PERT图是两种最常用的项目计划与进度安排工具。除此之外,还有关键路径法(CPM)图、里程碑图、工作分解结构(WBS) 等。一、主要工具介绍甘特图(Gantt Chart)形式:一种横向条形图,横轴表示时间,纵轴列出项目活动,条形长度表示活动的起止时间和持续时间。特点:直观展示活动的时间安排、重叠关系和进度状态。易于理解,适合用于资源分配和进度跟踪。但无法清晰显示活动之间的逻辑依赖关系,也难以直接识别关键路径。PERT图(Program Evaluation and Review Technique)形式:一种网络图,用节点(通常表示事件)和箭线(表示活动)来描绘活动之间的逻辑关系。特点:强调活动之间的前后顺序和依赖关系。通过三种时间估计(乐观、悲观、最可能)来计算活动工期和项目总工期,适用于不确定性较高的项目。可以识别关键路径和浮动时间,便于进行风险分析和资源优化。开始 → A(3) → B(5) → C(4) ↘ D(2) → E(3) → F(6)关键路径法(CPM)图形式:与PERT图类似,也是网络图,但通常用节点表示活动,箭线表示关系。特点:假设活动工期是确定的,侧重于计算项目的最短工期和关键路径。与PERT的主要区别在于:CPM使用单一时间估计,而PERT使用概率时间估计。 开始 | \ \ | \ \ A(3) B(5) C(4) | / \ | | / \ | D(2) E(3) F(6) \ / / \ \ / / \ H(1) I(3) \ / \ \ / \ J(2) K(2) \ / \ / L(2) | 结束其他工具里程碑图(Milestone Chart):仅标记关键里程碑(重大事件)的时间点,用于高层汇报和总体进度控制。工作分解结构(WBS):以层次化树状结构分解项目工作范围,不涉及时间安排,但是制定进度计划的基础。时间线图(Timeline):类似简化的甘特图,通常只显示主要事件或活动的时间线。{/card-default}在结构化分析中,DD(数据字典)中描述的数据对象通常被称为“数据元素”或“数据项”。{card-default label="软件成本估算模型" width=""}成本估算的本质是:在项目早期,预测完成项目所需的工作量(通常是人月/人天)和工期,进而推导出成本。估算模型主要基于一个核心关系:工作量 = (估算的软件规模) × (生产率相关的调节因子)根据对 “软件规模” 的度量方式不同,主要分为以下几大类:COCOMO模型(构造性成本模型)COCOMO基本是静态单变量模型COCOMO中级是静态多变量模型详细COCOMO分三层级加额外考虑功能点分析从用户外部视角度量软件提供给用户的“功能”规模,与技术实现无关。五类功能组件:外部输入:用户输入的数据。外部输出:系统输出的数据(报告、消息)。内部逻辑文件:系统内部维护的数据。外部接口文件:与其他系统交互的数据。外部查询:输入后立即得到输出的简单交互。计算步骤:识别并计数上述五类组件的数量。根据复杂度(低、中、高)为每个组件分配权重。计算未调整功能点数。考虑14个通用系统特性(如数据通信、性能要求等),计算值调整因子。最终功能点数 = 未调整FP × VAF。转换为工作量:工作量(人月) = 最终FP数 / 生产率(FP/人月)(生产率需根据组织历史数据或行业基准确定)用例点分析法适用于基于UML用例图进行需求分析的项目。计算步骤:计算未调整用例权重:根据参与者和用例的复杂度(简单、一般、复杂)分配权重并求和。计算技术复杂度因子和环境复杂度因子。计算调整后的用例点数。根据一个用例点对应多少人时的生产率经验值,估算总工作量。Putnam模型(SLIM)一种基于瑞利曲线分布和经验数据库的动态多变量模型。核心思想:工作量在项目生命周期中的分布符合特定曲线。需要输入:规模、项目时长、一个称为“生产率指数”的环境常量。可求解在给定规模下,理论上可能的最短开发时间。{/card-default}{card-default label="UML" width=""}UML(Unified Modeling Language,统一建模语言)可以通俗理解为 "软件工程师的标准化图纸" ,就像建筑师用蓝图设计房子一样,它是专门用来"画出"软件系统设计图的图形化语言。通俗解释:UML是啥?想象一下:建筑图纸 = UML图,让所有工人看懂同一套标准地铁线路图 = 展示功能流转的路径微信群聊记录 = 展示系统各部分如何按时间顺序"对话"UML就是用一套全球通用的图形符号,把看不见摸不着的软件系统变成"可视化"的设计图,让产品经理、程序员、客户都能用同一种语言沟通。UML到底有啥用?团队沟通的"普通话"避免开发中常见的"鸡同鸭讲":产品经理说"要有个智能推荐功能"程序员可能理解为"基于用户历史的推荐"老板心里想的是"像抖音那样的算法推荐"UML图把想法画出来,大家对着图讨论,一图胜千言。编码前的"彩排"没图纸就盖楼,容易盖到一半发现承重墙有问题。写代码前先画UML:提前发现逻辑漏洞和设计缺陷避免写完代码后大面积返工让复杂系统变得清晰可拆解项目的"设计日记"半年后回看代码,你可能忘记"当时为什么这么设计"。UML图就是你的技术档案:新人通过图快速理解系统架构维护和升级时有据可查降低人员交接的成本例子:设计电商系统时用用例图画出用户可以"浏览商品"、"下单"、"支付"用类图设计"商品"、"订单"、"用户"这些核心对象的关系用时序图分析下单时从前端到数据库的完整调用流程UML是软件行业的"标准工程语言"——用图说话,让复杂系统变得清晰可见,磨刀不误砍柴工,前期画图越充分,后期返工就越少。{/card-default}{card-default label="软件设计" width=""}设计阶段的两个步骤1.概要设计(高层设计):考虑 “系统由哪些部分组成,它们之间如何连接”。相当于画房屋的整体结构图(有几层、几个房间、楼梯在哪)。2.详细设计(底层设计):考虑 “每个部分内部具体如何实现”。相当于画每个房间的详细装修图(插座位置、水管走向、墙面材料)。两种设计方法的对比举个例子:设计一个“在线购物系统”结构化设计思路概要设计:模块划分:用户模块、商品模块、订单模块、支付模块。数据设计:设计用户表、商品表、订单表。接口设计:用户模块调用订单模块时需要传递哪些参数。详细设计:订单模块内部:用一个链表存储订单项,用快速排序对订单排序等。面向对象设计思路概要设计:识别类:Customer、Product、Order、Payment。类之间的关系:Order 包含多个 Product,Customer 拥有多个 Order。详细设计:细化 Order 类:属性 orderId、totalPrice;方法 calculateTotal()、generateInvoice()。应用设计模式:比如用 Factory 模式创建 Payment 对象。核心区别总结结构化设计:像流水线,关注步骤和功能模块,模块通常是函数或过程。面向对象设计:像乐高积木,关注事物和对象,模块通常是类和构件。{/card-default}{card-default label="耦合类型" width=""}从最理想(最松散)到最糟糕(最紧密)的顺序,介绍七种主要的耦合类型想象两个人(模块A和模块B)要合作完成一项工作。他们之间的“耦合”方式,决定了他们协作的紧密程度。七种耦合类型详解(从弱到强)非直接耦合关系:两个模块之间没有直接联系,它们通过一个主控模块或中间模块来协调。比喻:两个人完全不认识,他们所有的沟通和任务交接都通过一个项目经理(第三方) 来完成。他们彼此独立,互不影响。代码示例:模块A和模块B都只被主程序调用,它们之间不互相调用,也不共享任何数据。评价:最理想的耦合,独立性最强。数据耦合关系:模块之间通过参数传递基本数据类型的数据进行通信。比喻:一个人(A)需要另一个人(B)帮忙计算。A把原始数据(比如一串数字)交给B,B算好后把结果(一个数值)返回给A。A不关心B怎么算,B也不需要知道A的更多信息。代码示例:result = calculateTax(income, rate); 这里只传递了收入和税率两个基本数据。评价:理想的耦合,是设计时应该追求的主要方式。标记耦合关系:模块之间通过参数传递一个数据结构(如记录、对象),但被调用模块只用到该结构中的一部分数据。比喻:A把一个完整的公文包(数据结构)递给B,但B只需要里面的一份文件。公文包里的其他文件对B来说是无用信息。代码示例:传递一个完整的 Student 对象给 printStudentName() 函数,而这个函数其实只需要 student.name 属性。评价:比数据耦合稍差,因为产生了不必要的依赖(如果Student结构改变,即使printStudentName逻辑不变,也可能需要重新编译或检查)。控制耦合关系:一个模块向另一个模块传递了控制信息(如标志、开关),目的是直接影响后者的内部逻辑流程。比喻:A对B说:“这是数据,另外,如果遇到情况X,你就按方案1处理;否则按方案2。” A在控制B的决策。代码示例:processData(data, isUrgent),其中 isUrgent 是一个布尔标志,会改变 processData 的内部行为(如走快速通道还是普通通道)。评价:较强的耦合,因为调用者需要了解被调用者的内部逻辑分支。应尽量避免。外部耦合关系:多个模块都依赖同一个外部环境或全局约定,如共享的通信协议、数据格式、硬件接口等。比喻:A和B都使用同一种“行业黑话”或同一种特定工具进行交流。他们之间不直接依赖,但都依赖这套外部规则。代码示例:多个模块都读写同一个特定格式的XML文件,或者都调用同一个操作系统提供的特定API。评价:耦合度较高,但有时不可避免。关键在于将外部依赖集中管理。公共耦合关系:多个模块都全局性地访问和修改同一块公共数据区(如全局变量、共享数据库表、公共内存)。比喻:A和B都在一块公共白板上读写信息。A写的东西可能被B无意擦掉或覆盖,反之亦然。他们的行为通过这块白板紧紧绑在一起。代码示例:多个函数都直接读写一个全局变量 globalConfig。代码影响:极难调试和理解,因为任何一个模块的行为都可能被远处另一个模块的修改所影响。评价:糟糕的设计,应严格限制使用。内容耦合关系:一个模块直接修改或依赖另一个模块的内部内容(如直接跳转到另一个模块的内部标号、修改另一个模块的内部数据)。比喻:A不仅给B下指令,还直接动手篡改B的笔记或大脑里的想法。代码示例:模块A使用 goto 语句跳转到模块B内部的某个标签;或者通过指针黑客手段直接修改模块B的私有变量。评价:最差、最致命的耦合,完全破坏了模块的封装性。现代编程语言(如Java, C#)的语法特性已基本杜绝了这种情况。核心设计原则在软件设计中,我们的目标是:追求低耦合(松耦合),尤其是数据耦合;警惕并减少控制耦合;尽可能避免公共耦合和内容耦合。 低耦合的系统就像用乐高积木搭成的建筑,一块坏了可以轻松替换,而不像用水泥浇灌的一整块墙体,牵一发而动全身。{/card-default}{dotted startColor="#ff6c6c" endColor="#1989fa"/}{callout color="#f0ad4e"}面向对象的分析方法面向对象的分析(Object-Oriented Analysis,OOA)是一种将现实世界的问题域概念转化为面向对象软件模型的系统化方法。它关注于理解问题、识别关键对象及其关系,并构建一个精确、可验证的分析模型,作为后续设计的基础。一、核心思想:像认识世界一样认识系统OOA 的核心是 “对象” —— 一种将数据(属性) 和行为(方法) 封装在一起的实体。它模拟了人类认知现实世界的方式:世界由各种对象组成(如:学生、课程、订单)。对象具有特征(属性)和能力(方法)。对象之间通过消息传递进行交互。对象可以分类,形成类(具有相同属性和行为的对象的抽象)。二、四大基本原则(支柱)多态性允许我们以统一的方式处理不同的对象,而客户端代码不需要知道具体是哪个子类。一个对象对应多张数据库表描述的是数据持久化模式子类必须覆盖父类中的抽象方法(否则子类也是抽象类),并且可以覆盖非抽象方法(重写)。这属于继承的规则对象在运行时可以改变其类型(如某些动态语言),但这属于动态类型或反射特性三、OOA的主要产出模型(UML图)OOA 使用一套标准化的可视化模型来描述系统,最常用的是 UML(统一建模语言) 的以下图表:四、OOA的一般步骤OOA 是一个迭代和增量的过程,通常包括以下活动:需求获取与用例建模:与用户沟通,识别 参与者(Actor)和 用例(Use Case)。绘制 用例图,编写主要的 用例描述(基本事件流、备选事件流)。识别对象与类:从用例描述、需求文档中寻找名词短语,初步筛选出候选对象。将对象抽象成 类,确定其 职责(应该做什么)。建立静态模型(类图):为每个类识别 属性 和 操作。分析类之间的关系:关联(一个学生选多门课)、聚合/组合(汽车由发动机和轮子组成)、泛化(研究生是一种学生)、依赖。建立动态模型:针对重要的或复杂的用例,绘制 序列图 或 协作图,详细描述对象之间的交互过程。对于状态复杂的类(如订单、账户),绘制 状态图。对于涉及复杂业务流程的用例,绘制 活动图。定义系统行为与规则:使用 文本补充 或 OCL(对象约束语言) 描述业务规则、约束条件和非功能性需求。五、OOA vs. 结构化分析六、优势总结与现实一致:更贴近人类对现实世界的认知方式,易于与领域专家沟通。稳定性高:对象相对于功能更为稳定,使得分析模型在需求变化时更具弹性。可复用性好:识别出的类和分析模式,可在后续项目或同一项目的其他部分复用。支持迭代开发:OOA模型天然支持增量式、迭代式的开发过程。贯穿整个生命周期:从分析到设计、实现,概念和模型可以平滑过渡,降低了阶段转换的损耗。总而言之,面向对象的分析方法是一种以对象为核心、通过一系列可视化模型来系统化地理解和定义系统需求的强大方法。它是现代软件工程,尤其是复杂系统开发中不可或缺的一环。{/callout}动态绑定:过程调用与具体执行的代码在程序运行时才确定,常见于面向对象中的多态(如虚函数调用)。静态绑定:过程调用与具体执行的代码在编译时即可确定,如普通函数调用或非虚方法调用。这两个概念体现了程序设计中绑定时间的差异,动态绑定提供了灵活性,而静态绑定效率更高。{callout color="#f0ad4e"}系统转换(System Conversion)系统转换是指用新建成的信息系统替换旧的现有系统,并使其正式投入运行的过程。它是系统实施阶段最关键的步骤,决定了新系统能否成功“上线”。你可以把它想象成 “给一辆高速行驶的汽车更换发动机” —— 必须保证汽车不停下来,且新发动机要能无缝接替工作。一个生动的例子:银行核心系统升级一家银行要升级其最核心的存取款交易系统。直接转换?绝对不行!万一新系统宕机,全国所有ATM和柜台都将瘫痪,引发社会危机。并行转换?这是最可能的选择。尽管成本极高(需要双倍算力、人力核对每一笔交易),但能保证万无一失。在并行期,每一笔交易既走旧系统,也走新系统,结果必须完全一致。运行一个月后,数据100%吻合,再在某个周末凌晨,切断旧系统。分段转换?可能按业务分(先换存款,再换贷款),或按地区分(先换上海分行,再换北京分行)。试点转换?可以先选择一个信息化基础好的二级分行进行试点。总结系统转换是技术、管理和心理的综合工程。成功的转换 = 正确的策略 + 详尽的数据迁移计划 + 充分的用户培训 + 周全的应急预案。其终极目标是:让新系统平稳“着陆”,用户无感或体验提升,业务连续不中断。{/callout}在项目管理(如PMP)和风险管理体系中,设定优先级最常用的指标是风险暴露(也叫风险敞口或风险重要度)。它是一个综合值,计算公式通常为:风险暴露 = 风险概率 × 风险影响为什么不是单纯的“影响”或“概率”?风险影响:只考虑后果的严重程度。但如果一个“灾难级”影响几乎不可能发生,优先级其实不高。风险概率:只考虑发生的可能性。但如果一个“极可能”发生的事故影响很小,同样不需要优先处理。风险控制:这是应对措施,不是设定优先级的依据。简单对比记忆:风险影响:这事有多惨?风险概率:这事多大概率发生?风险暴露:综合以上两者,算出这笔风险账到底有多大。优先级正是依据这个综合值从高到低排的。因此,虽然“影响”和“概率”是重要参数,但题目问的是“设定优先级的根据”,标准答案是两者的乘积——风险暴露。
2026年02月10日
4 阅读
0 评论
0 点赞
2026-02-09
逻辑地址转换计算
在分页存储管理中,逻辑地址通过页表映射为物理地址。页面大小决定了地址的划分方式:页内偏移位数 = log2(页面大小)逻辑地址高位表示页号,低位表示页内偏移。假设:一个小区有10栋楼(物理块),每栋楼有100个房间(每页大小100)。你朋友住“3栋16室”(逻辑地址:页号3,偏移16)。但实际小区规划变了,3栋其实在物理上是第6栋(页表:页号3→块号6)。那么物理地址 = 第6栋第16室。页面大小4KB = 4096字节的十六进制是 0x1000(因为 4096 = 16^3 = 0x1000)。为什么是0x1000?十六进制每1位代表4位二进制,3位十六进制就是12位二进制,2^12=4096。所以页内偏移范围:0x000 到 0xFFF(共4096个位置)。逻辑地址 1D16H 怎么拆分?因为一页大小是 0x1000,所以:页号 = 地址 ÷ 0x1000(整除取商)偏移 = 地址 % 0x1000(取余数)1D16H ÷ 1000H = 1 ... D16H所以页号=1,偏移=D16H。为什么除1000H?因为每0x1000个地址换一页,就像每1000个房间换一栋楼。1D16H 在第1页(因为1xxx),偏移是D16。逻辑地址:1D16H物理地址:3D16H它们只有“页号部分”从1变成3(根据页表映射),偏移部分D16H完全不变。这就是分页内存管理的核心:逻辑地址 = 页号 + 页内偏移物理地址 = 物理块号 + 同一偏移偏移永远不变,因为页内位置在物理页中相同位置。{card-default label="十进制转换十六进制" width=""}十进制转十六进制方法(以题中相关数字为例):基本方法:连续除以16,倒取余数用十进制数不断除以16,记下每次的余数(0~15),直到商为0。将余数从最后一个到第一个排列,就是十六进制数。余数10~15分别用字母A~F表示。举例:十进制 45 转十六进制:÷ 16 = 2 余 13(13是D)÷ 16 = 0 余 2余数倒序:2、D → 2DH十进制 4096 转十六进制(本题页面大小):÷ 16 = 256 余 0÷ 16 = 16 余 0÷ 16 = 1 余 0÷ 16 = 0 余 1余数倒序:1、0、0、0 → 1000H{/card-default}CPU的访问速度通常遵循以下顺序(从快到慢):寄存器(Register):位于CPU内部,速度最快。高速缓存(Cache):分为L1、L2、L3等,L1最快,L2次之,L3再次之。内存(RAM):也称为主存,速度比缓存慢。硬盘(包括SSD和HDD):外存,速度远慢于内存。网络存储:通过网络访问的存储设备,速度最慢。需要注意的是,访问速度通常用纳秒(ns)或毫秒(ms)来衡量,并且随着技术的发展,具体数值会变化,但相对顺序基本不变。{card-default label="cache相关" width=""}Cache 工作三步曲:查书桌(Cache查找)当 CPU 需要读数据时:它先看看自己手边的“小书桌”(Cache)上有没有这本书。如果有(Cache命中),直接拿来看 —— 极快。如果没有(Cache未命中),就得去“大书库”(内存)里找 —— 慢很多。上书桌(Cache加载)如果数据不在 Cache 里:CPU 去内存把需要的数据“拿过来”。同时会把这本书连同它旁边的几本书一起放到书桌上(局部性原理:下次很可能用附近的数据)。如果书桌满了,就扔掉一本最久不看的(缓存替换策略,如LRU)。保持整洁(Cache一致性)当 CPU 修改了书桌上的数据:需要标记“这本书和书库里的不一样了”。之后要么马上把修改写回书库(写通),要么先记着,等换书时再写回(写回)。确保其他部件(如其他CPU核心)不会读到过期数据。Cache 是 CPU 和内存之间的“速度缓冲器”,利用局部性原理提前准备数据,让 CPU 绝大部分时间不用等内存。"地址映像是什么"。这通常指的是在计算机体系结构中,将主存地址映射到Cache地址的方式。地址映像是Cache组织中的一个核心概念,它决定了主存中的块可以被放置在Cache中的哪个位置。地址映像的定义:地址映像是指将主存地址空间映射到Cache地址空间的规则或方法。它决定了主存中的每一个数据块可以存放在Cache中的哪些位置。常见的地址映像方式有三种:直接映射:主存中的每个块只能被放置到Cache中唯一的一个特定位置。通常,Cache被分成若干行,主存块号对Cache行数取模来决定放置到哪一行。这种方法简单,但容易发生冲突(即使Cache其他行空闲,也可能因为两个主存块映射到同一行而发生替换)。全相联映射:主存中的任何一个块可以被放置到Cache中的任意一行。这种方法灵活,冲突最少,但查找时需要比较所有行的标签,硬件成本高。组相联映射:将Cache分成若干组,每组包含若干行。主存块首先映射到特定的组(通常是通过取模运算),然后可以放置在该组内的任意一行。这是直接映射和全相联映射的折中,常用的有2路、4路组相联等。此外,地址映像还涉及到替换算法(当Cache满时选择哪个块被替换出去)和写策略(当数据被修改时如何保持Cache与主存的一致性)。通俗比喻:直接映射:停车场每辆车有固定车位(即使其他空着,也不能停)。全相联映射:停车场任意空位都可停。组相联映射:停车场分几个区,车只能停在自己区内,但区内任意空位可选。地址映像是Cache设计的核心,直接影响命中率、速度和硬件成本。{/card-default}什么是磁盘碎片?想象一个图书馆(磁盘)。一开始,书(文件)都整整齐齐、连续地放在书架上。但随着时间的推移,有人借书还书(删除和写入文件),还回来的书可能被塞进任何有空隙的地方。久而久之,一本完整的书(比如一个100页的小说)可能被拆成了几部分,散落在图书馆的不同角落。磁盘碎片 就是指一个文件的数据被分割成多个不连续的片段(称为“碎片”),分散存储在磁盘的不同物理位置。文件碎片:单个文件内容不连续。空闲空间碎片:磁盘上可用的空闲空间也是零散的小块,无法容纳较大的新文件。为什么会产生碎片?文件的创建、删除和修改:这是主因。当你删除一个文件时,它原来占用的空间就被释放出来,变成一个个“空洞”。当你保存一个新文件时,操作系统会尝试把这些“空洞”填满。如果新文件很大,它就会被拆开,分别存入多个不相邻的“空洞”中。文件系统管理机制:早期的FAT32等文件系统更容易产生碎片。现代文件系统(如NTFS、APFS、ext4)在设计上通过更智能的空间分配算法(如预分配、延迟分配)来减少碎片产生,但无法完全避免。磁盘碎片带来的问题主要问题是 性能下降,尤其对于传统的机械硬盘(HDD) 影响巨大。机械硬盘(HDD):数据存储在高速旋转的盘片上,通过磁头来回移动进行读写。当读取一个碎片化的文件时,磁头需要在盘片的不同位置之间来回跳动(寻道)并等待盘片旋转到正确位置(旋转延迟)。这大大增加了访问时间,导致系统变慢、文件打开迟缓、程序加载时间长。固态硬盘(SSD):SSD没有机械部件,通过电子信号直接访问存储芯片,因此“寻道时间”几乎为零。碎片对SSD的读取性能影响微乎其微。什么是磁盘碎片整理?磁盘碎片整理 就是一个将分散存放的文件碎片重新整理、组合,并按照连续(或尽可能连续)的方式写入磁盘,同时合并空闲空间的过程。它的核心目标:减少机械硬盘的寻道时间和旋转延迟,从而提升I/O性能。整理过程一般包括:分析:扫描磁盘,查看文件和空闲空间的碎片化程度。移动与重组:将碎片化的文件内容读取到内存中。在磁盘上找到一个足够大的连续空闲区域。将该文件的全部内容连续地写入这个新区域。标记原区域为空闲。合并空闲空间:将整理过程中产生的多个小空闲块合并成少数几个大的连续空闲块,便于未来存放大文件。位示图 或 位图,它是操作系统中用于管理磁盘空间或内存空间的一种经典且高效的数据结构。 其核心思想是:用一串二进制位(0或1)的集合,来直观映射和表示一大片存储资源中每个最小分配单元的使用状态。 想象你有一大张方格纸(代表整个磁盘或内存),每个格子代表一个最小的存储单位(如一个磁盘块 或一个内存页框)。你需要记录哪个格子是空的,哪个格子已经被用了。 位示图就是为这张方格纸配的一把“尺子”或“图例”: 这把“尺子”本身也是一个线性的序列,序列中的每一个位(bit) 对应方格纸上的一个格子。 该位的值仅有两种状态: 0:表示对应的那个格子是 空闲的。 1:表示对应的那个格子是 已占用的。 因此,位示图就是存储资源使用情况的“地图”或“状态表”。 主要用途 磁盘空闲空间管理 这是位示图最经典的应用。文件系统(如早期的FAT, Unix文件系统)用一块连续的磁盘区域来存放整个磁盘的位示图。 当需要创建一个新文件时,操作系统就扫描位示图,寻找值为 0 的位,计算出对应的空闲磁盘块,分配给文件,并将这些位标记为 1。 当删除文件时,系统将文件占用的块对应的位重新置为 0。 内存分页管理 在分页式内存管理中,物理内存被划分为固定大小的页框。 操作系统维护一个内存位示图,其中每一位对应一个物理页框,记录其是分配给了进程还是空闲。 工作原理(以磁盘管理为例) 假设一个磁盘共有 N 个块,块号从 0 到 N-1。 建立位示图:创建一个长度为 N 位的位向量。例如,磁盘有4096个块,就需要4096位的位示图,占用 4096/8 = 512 字节的存储空间。 状态映射: 块 i 的状态 = 位示图中第 i 位的值。 位示图[i] = 0 -> 块 i 空闲。 位示图[i] = 1 -> 块 i 已用。 分配磁盘块: 当需要 k 个连续(或不连续)的磁盘块时,操作系统扫描位示图,找出 k 个值为 0 的位。 通过简单的计算(块号 = 位序号),即可得到空闲块的物理块号。 将这些块分配给文件,并把对应的位置 1。 释放磁盘块: 当文件被删除,释放其占用的块时,根据块号找到位示图中对应的位,将其置 0 即可。 位示图是操作系统资源管理的基石性技术之一。它将存储空间的分配状态抽象成一张简单的二进制地图,使得操作系统能够以极小的开销、高效地掌握全局资源情况,从而完成空间的分配与回收。您之前问的“磁盘碎片整理”,其底层也需要依赖类似位示图这样的结构来了解磁盘上哪些地方是空闲的,以便移动和重组文件。 优点: 空间效率高:管理开销非常小,一个二进制位就能管理一个存储单元。 查找相对高效:寻找第一个或连续的空闲块,可以通过快速扫描位向量完成。现代CPU有专门指令(如位操作指令)能加速此过程。 概念简单清晰:实现和理解都比较容易。 缺点: 性能问题:当存储空间非常大时,位示图本身也会变得很大。为了分配空间,可能需要扫描很长的位序列才能找到空闲块(尽管可以通过缓存或优化算法缓解)。 不适用于特大容量:对于超大规模的磁盘(如数PB),位示图本身可能占用数MB甚至数十MB的内存,且扫描效率下降。
2026年02月09日
4 阅读
0 评论
0 点赞
2026-02-04
此内容被密码保护
加密文章,请前往内页查看详情
2026年02月04日
1 阅读
0 评论
0 点赞
2026-02-04
此内容被密码保护
加密文章,请前往内页查看详情
2026年02月04日
1 阅读
0 评论
0 点赞
2026-02-04
操作系统理论
一、进程的引入:为什么需要进程?问题背景:早期计算机一次只能运行一个程序。比如,你有一个计算程序和一个下载程序,你想边计算边下载,怎么办?解决方案:引入进程的概念,让多个程序可以“同时”运行。实际上,在单核CPU上,是通过快速切换来实现的,但给用户的感觉是同时运行。例子:你打开音乐播放器听歌,同时用浏览器上网。操作系统为每个程序创建了一个进程,并轮流执行它们(时间片轮转)。虽然CPU同一时刻只能执行一个进程,但由于切换很快,你感觉音乐和网页同时都在运行。二、进程是什么?具体例子:想象一个厨房(CPU)里有一个厨师(进程)。厨师要做一道菜(程序),他需要菜谱(代码)、食材(数据)、厨具(资源)。菜谱指明了步骤,但厨师在切菜的时候可能被叫去炒菜(切换),那么他必须记住切到哪了(保存现场),等他回来再接着切。进程的定义:进程是程序的一次执行过程,是系统进行资源分配和调度的基本单位。它包括代码、数据、程序计数器、寄存器、堆栈等。三、进程的状态与切换面对的问题:进程在运行中,有时需要等待外部事件(比如等待用户输入、等待磁盘读取数据),如果让CPU一直等待,就浪费了。如何提高CPU利用率?解决办法:让进程在等待时让出CPU,给其他就绪的进程使用。这就引入了进程状态。例子:假设有两个进程A和B。A需要从磁盘读取数据,于是操作系统将A的状态改为“阻塞”,并让B运行。当磁盘数据准备好后,再将A的状态改为“就绪”,等待CPU调度。典型的状态转换:就绪:进程已准备好,只等CPU。运行:进程正在CPU上执行。阻塞:进程等待某个事件(如I/O完成)。状态转换图:新建 → 就绪 ↔ 运行 → 终止 ↓ 阻塞四、进程的创建与终止创建:通过系统调用(如fork)创建新进程。例子:在Linux终端中,你输入ls命令,shell进程会调用fork创建一个子进程,然后子进程调用exec执行ls程序。终止:进程完成任务后,通过exit系统调用终止,释放资源(除了进程控制块PCB,最后会被父进程回收)。五、进程间通信(IPC)问题:进程之间是隔离的,不能直接访问对方的内存。但如果需要协作,比如一个进程生产数据,另一个进程消费数据,怎么办?解决办法:操作系统提供进程间通信的机制。具体例子:管道(pipe):就像一条水管,一端写,另一端读。例如,在shell中执行ls | grep .txt,shell创建两个进程,一个执行ls,一个执行grep,并用管道连接起来。ls的输出直接作为grep的输入。消息队列:进程A发送一个消息到队列,进程B从队列中读取。类似于发邮件。共享内存:两个进程可以访问同一块内存区域。这是最快的IPC方式,但需要同步机制(如信号量)来避免冲突。信号(signal):一个进程可以向另一个进程发送信号,比如按下Ctrl+C发送SIGINT信号终止进程。六、进程同步问题:当多个进程共享资源(比如共享内存、文件)时,可能会产生冲突。例子:有两个进程A和B,它们都要往同一个文件里写数据。如果A写了一半,B也开始写,那么文件内容就会混乱。解决办法:使用同步机制,确保同一时刻只有一个进程访问共享资源。具体方法:互斥锁(Mutex):进程在访问共享资源前加锁,访问完后解锁。其他进程必须等待锁释放。信号量(Semaphore):一个计数器,用来控制多个进程对共享资源的访问。比如,只有3个资源,信号量初始值为3,每有一个进程占用一个资源,信号量减1,当减到0时,其他进程必须等待。条件变量:用于等待某个条件成立。七、进程调度问题:有多个就绪进程,但CPU只有一个,先运行哪个?调度算法例子:先来先服务(FCFS):就像排队买票,先来的先服务。但如果有长进程先到,短进程就要等很久。短作业优先(SJF):优先运行估计运行时间最短的进程。这可以减少平均等待时间,但长进程可能一直得不到运行(饥饿)。时间片轮转(RR):每个进程分配一个时间片(比如100ms),时间片用完后,进程被剥夺CPU,放到就绪队列末尾。这样每个进程都能得到执行,但上下文切换开销较大。多级反馈队列:结合了多种算法。有多个队列,每个队列优先级不同,时间片大小也不同。新进程进入最高优先级队列,如果时间片用完还没结束,就降到下一级队列。这样短进程可以很快完成,长进程也会得到执行。八、上下文切换问题:当CPU从一个进程切换到另一个进程时,需要保存当前进程的状态,以便下次恢复。例子:进程A正在运行,时间片用完,操作系统要切换到进程B。那么,操作系统必须保存A的寄存器、程序计数器等状态到A的PCB中,然后从B的PCB中恢复B的状态,让B继续运行。开销:上下文切换需要时间,如果切换太频繁,CPU的有效工作时间就会减少。九、多线程与进程的关系问题:进程的创建、切换开销较大,而且进程间通信复杂。如果我们要在一个程序内同时做多件事,比如一个文档编辑器,一边接收用户输入,一边自动保存,一边进行拼写检查,怎么办?解决办法:引入线程。线程是进程内的执行单元,一个进程可以有多个线程,它们共享进程的资源(如内存、文件),但各自有独立的栈和寄存器。例子:一个Web服务器,每来一个客户端请求,就创建一个线程来处理。这样,多个请求可以同时被处理(在多核CPU上真正并行,单核上也是并发)。因为线程共享进程的内存,所以创建线程和线程间通信的开销比进程小。十、总结进程是操作系统资源分配的基本单位,它使得多个程序可以并发执行,提高了系统资源的利用率。但进程的创建、切换、通信都有开销,因此后来又引入了线程。理解进程的关键是理解它的状态转换、调度、同步和通信机制,这些都是为了解决多任务环境下的资源竞争和协作问题。{card-default label="线程概念" width=""}线程:进程内部的“多任务协作”一、为什么需要线程?—— 解决“进程内部并发”的问题问题场景:你使用Word编辑文档,希望同时进行拼写检查、自动保存、用户输入响应。如果用多进程实现,那么这些任务之间共享数据(文档内容)会非常麻烦(需要进程通信),而且创建进程的开销较大。解决方案:在一个进程内部创建多个线程,每个线程执行不同的任务,共享同一份数据。具体实现:线程是进程内的执行单元,一个进程可以有多个线程同一进程的线程共享内存空间(代码段、数据段、打开的文件等)每个线程有独立的栈和程序计数器例子:Word进程中的多线程Word进程: 线程1:处理用户输入(键盘、鼠标) 线程2:定时自动保存 线程3:拼写和语法检查 线程4:打印文档二、线程是什么?—— 进程的“分身”比喻:餐厅厨房里的多个厨师整个厨房 = 进程(拥有食材、厨具等资源)每个厨师 = 线程(共享厨房资源,各自做一道菜)厨师间的协作 = 线程通信(直接说话,因为共享空间)具体例子:Web服务器处理多个客户端请求单进程多线程模型: 主线程:监听端口,接受连接 工作线程1:处理客户端A的请求 工作线程2:处理客户端B的请求 工作线程3:处理客户端C的请求 所有线程共享服务器资源(如缓存、数据库连接池) 线程共享的“公共空间” vs 私有的“个人空间”进程内的线程共享资源指的是:进程的地址空间中的大部分区域,包括代码段、数据段、堆、以及打开的文件描述符等。而每个线程拥有自己独立的栈空间(用于保存局部变量和函数调用信息)以及线程私有的上下文(如寄存器状态、程序计数器等)。具体来说:代码段:所有线程执行相同的代码,它们共享进程的代码段(即程序的可执行代码)。数据段:包括全局变量和静态变量,所有线程都可以访问和修改这些变量,因此需要同步机制来避免竞态条件。堆:动态分配的内存(如malloc/new分配的内存)是共享的,所有线程都可以访问堆中的同一块内存。文件描述符:进程打开的文件、网络套接字等,所有线程共享这些文件描述符,因此一个线程打开的文件,另一个线程也可以读写。信号处理程序:进程设置的信号处理函数是共享的,但每个线程可以有自己独立的信号屏蔽字。当前工作目录:进程的当前工作目录是共享的,一个线程改变工作目录会影响其他线程。用户ID和组ID:进程的用户ID和组ID是共享的。每个线程私有的资源包括:线程ID:每个线程有唯一的ID。寄存器状态:包括程序计数器、栈指针等,每个线程有自己的寄存器状态。栈:每个线程有自己独立的栈,用于存储局部变量、函数调用链等。线程之间不能直接访问其他线程的栈。信号屏蔽字:每个线程可以独立设置阻塞或解除阻塞某些信号。线程私有数据:有些系统支持线程私有数据(Thread Local Storage, TLS),即每个线程可以拥有某些数据的独立副本。理解线程共享哪些资源非常重要,因为这直接影响到多线程程序的设计和同步机制的使用。例如,共享的数据需要加锁保护,而线程私有的数据则不需要。三、线程 vs 进程 —— 详细对比四、线程同步——解决“共享数据混乱”问题问题场景:两个线程同时操作同一个银行账户,一个取钱,一个存钱,如果没有同步,账户余额可能会出错。互斥锁(Mutex)—— 卫生间一次只能一个人用// 银行账户例子 int balance = 1000; // 共享变量 pthread_mutex_t lock; // 线程A:取钱100 pthread_mutex_lock(&lock); int temp = balance; temp -= 100; balance = temp; pthread_mutex_unlock(&lock); // 线程B:存钱200 pthread_mutex_lock(&lock); int temp = balance; temp += 200; balance = temp; pthread_mutex_unlock(&lock);条件变量(Condition Variable)—— 排队等空位// 生产者-消费者问题(线程版) queue buffer; // 共享缓冲区 pthread_mutex_t mutex; pthread_cond_t cond_full; // 缓冲区满的条件变量 pthread_cond_t cond_empty; // 缓冲区空的条件变量 // 生产者线程 pthread_mutex_lock(&mutex); while (buffer.is_full()) { pthread_cond_wait(&cond_full, &mutex); // 等待缓冲区不满 } buffer.push(item); pthread_cond_signal(&cond_empty); // 通知消费者缓冲区不空了 pthread_mutex_unlock(&mutex); // 消费者线程 pthread_mutex_lock(&mutex); while (buffer.is_empty()) { pthread_cond_wait(&cond_empty, &mutex); // 等待缓冲区不空 } item = buffer.pop(); pthread_cond_signal(&cond_full); // 通知生产者缓冲区不满了 pthread_mutex_unlock(&mutex);读写锁(Read-Write Lock)—— 图书馆规则场景:多个线程同时读一个配置,偶尔有线程写配置 规则: 1. 可以多个线程同时读 2. 写的时候不能有任何其他读或写 实现: 读写锁有三种状态:读模式加锁、写模式加锁、不加锁 写模式加锁时,其他线程的读或写都会被阻塞 读模式加锁时,其他线程的写会被阻塞,但读可以继续五、线程池——避免“频繁创建销毁线程”的开销问题场景:Web服务器为每个请求创建一个新线程,请求处理完后销毁。当请求很多时,创建和销毁线程的开销很大。解决方案:预先创建一组线程,重复使用。线程池组成:1. 任务队列:存放待处理的任务 2. 工作线程:从任务队列取任务执行 3. 管理器:创建、销毁线程,调整线程数量例子:Java中的线程池ExecutorService pool = Executors.newFixedThreadPool(10); // 创建10个线程的池 // 提交100个任务 for (int i = 0; i < 100; i++) { pool.submit(new Task(i)); } pool.shutdown(); // 关闭线程池优势:降低资源消耗:重复利用已创建的线程提高响应速度:任务到达时,线程已存在,无需等待创建提高可管理性:可以统一管理线程调试多线程问题常见问题与排查工具问题1:竞态条件症状:结果不确定,每次运行可能不同 工具:ThreadSanitizer (TSan)、Helgrind问题2:死锁症状:程序卡住,不响应 工具:jstack (Java)、pstack、gdb 例子:jstack <pid> | grep -A 10 deadlock问题3:活锁症状:线程忙碌但无进展 例子:两个线程互相"礼让",都不断重试失败问题4:资源泄漏症状:内存或线程数持续增长 工具:jvisualvm、top -H、pmap总结:线程使用指南黄金法则:能不共享就不共享:减少共享数据,避免同步问题能无锁就无锁:使用原子变量、不可变对象必须同步时,粒度尽量小:减少锁竞争优先使用高级工具:线程池、并发集合、同步工具类线程是强大的并发工具,但"能力越大,责任越大"。理解其原理,遵循最佳实践,才能写出正确高效的多线程程序。现代软件中的线程使用案例1:Android应用的主线程与工作线程// Android中,UI操作必须在主线程(UI线程) class MainActivity : AppCompatActivity() { fun loadUserData(userId: String) { // 在后台线程执行耗时操作 thread { val userData = apiService.getUserData(userId) // 网络请求(可能耗时) // 回到主线程更新UI runOnUiThread { textView.text = userData.name // UI操作必须在主线程 progressBar.visibility = View.GONE } } } }规则:主线程只处理UI更新和用户交互,耗时操作(网络、数据库、计算)必须放在工作线程。案例2:Redis的单线程模型(为什么快?)Redis核心是单线程的,但性能极高,原因: 1. 纯内存操作:无磁盘I/O阻塞 2. 非阻塞I/O:使用epoll多路复用 3. 避免锁竞争:单线程不需要同步 4. 数据结构优化:高效的数据结构 适用场景:高并发、数据量小、读写频繁 不适用场景:复杂计算、大数据处理案例3:Nginx的多进程+多线程混合模型Nginx架构: ┌─ Master进程(管理)─────────────────────┐ │ ├─ Worker进程1(独立) │ │ │ ├─ 线程1:处理连接A │ │ │ ├─ 线程2:处理连接B │ │ │ └─ 线程池:处理磁盘I/O │ │ ├─ Worker进程2(独立) │ │ └─ Worker进程N(独立) │ 设计优点: 1. 进程隔离:一个Worker崩溃不影响其他 2. 线程共享:同一Worker内线程共享缓存 3. 充分利用多核:每个Worker绑定一个CPU核心{/card-default}虚拟存储管理系统的基础是程序的局部性理论,这个理论的基本含义是指程序执行时往往不会均匀地访问主存储器单元。根据这个理论,Denning 提出了工作集理论。工作集是进程运行时被频繁访问的页面集合。在进程运行时,如果它的工作集页面都在主存储器(内存)内,能够使该进程有效地运行,否则会出现频繁的页面调入/调出现象。{card-default label="局部性原理" width=""}局部性原理是虚拟存储技术的理论基础,包括两个方面:时间局部性:如果程序中的某条指令或数据被访问,那么不久之后它很可能再次被访问。典型例子包括循环、重复调用的函数等。空间局部性:如果程序访问了某个存储单元,那么其附近的存储单元也可能很快被访问。典型例子包括顺序执行的指令、数组的连续访问等。基于局部性原理,程序运行时只需将当前使用的部分页面装入内存,而非全部程序,从而实现虚拟存储器的高效管理。{/card-default}{card-default label="工作集理论" width=""}{/card-default}{card-default label="抖动(Thrashing)" width=""}当系统内存资源不足时,进程的工作集无法完全驻留内存,导致频繁的页面置换(调入/调出)。此时,CPU 大量时间用于处理缺页中断,实际工作效率下降,这种现象称为“抖动”。防止抖动的常用方法是采用工作集模型,确保每个活跃进程的工作集常驻内存,或采用页面置换算法(如工作集时钟算法)来动态调整内存分配。{/card-default}{card-default label="虚拟存储管理" width=""}虚拟存储技术允许程序部分装入内存即可运行,通过缺页中断机制和页面置换算法,实现内存的自动管理。其核心思想正是基于局部性原理:程序在一段时间内访问的页面相对集中,因此只需将这部分页面保留在内存中即可高效运行。工作集理论为虚拟存储器的内存分配和置换策略提供了重要指导。{/card-default}进程通信PV操作与信号量核心知识点总结初值=0 意味着初始状态没有任何可用资源,事件未发生,必须等待通知P操作 就像"瞅一眼桌子",发现没饭就睡觉(进程阻塞)V操作 就像"外卖员按门铃",把你叫醒继续执行(进程唤醒)原子性保证:// 假设S=1,两辆车同时P操作 车A: 读取S=1 → S--=0 → 进入停车场 车B: 读取S=1 → S--=0 → 也进入停车场 ❌ // 结果:一个车位停两辆车!碰撞!正确顺序:先P资源信号量,再P互斥信号量原理:如果先拿锁,当资源耗尽时,其他进程无法进入临界区释放资源,形成死锁初值=1:商场厕所(互斥访问)场景:只有一间厕所,拉屎要排队数据库行锁:1个线程修改,其他线程阻塞打印机互斥:1个人打印,其他人排队技术映射:保护临界区,一次只允许一个进程访问初值=N:停车场(资源池)场景:10个车位,先到先得图书馆座位:初值=500个座位数据库连接池:初值=5个连接线程池任务队列:初值=100个任务容量资源申请顺序不一致Process1 { P(A); P(B); ... V(B); V(A); } // 先A后B Process2 { P(B); P(A); ... V(A); V(B); } // ❌ 先B后A // 结果:Process1占A等B,Process2占B等A → **死锁!**正确:全局统一起始顺序Process1 { P(A); P(B); ... } Process2 { P(A); P(B); ... } // ✅ 顺序一致信号量负值理解错误题目:3个进程共享1台打印机,信号量初值=1,当前值=-2,问什么情况?❌ 错误答案:有2台打印机空闲(完全错误!)✅ 正确答案:2个进程在等待打印机(|-2|=2)编写PV操作(黄金顺序)Process { 准备工作; P(资源信号量); // 1️⃣ 先检查资源是否可用 P(互斥信号量); // 2️⃣ 再进入临界区(上锁) 操作共享资源; // 3️⃣ 临界区代码 V(互斥信号量); // 4️⃣ 先出临界区(解锁) V(通知信号量); // 5️⃣ 再通知其他进程 后续操作; }📌 初值选择:互斥用1,同步用0,资源就是N📌 PV本质:P是申请,不够就等;V是释放,唤醒他人📌 操作顺序:先资源后锁,先解锁后通知📌 信号量值:正数可用,零刚好用,负数等几📌 死锁避免:统一申请顺序,限制最大并发进程调度知识点进程调度本质核心问题:CPU只有一个,成百上千个进程要运行,按什么顺序执行?生活类比:餐厅只有一个厨师,怎么安排炒菜顺序才能让顾客最满意?{card-default label="六大调度算法" width=""}1.先来先服务(FCFS)原理:排队买奶茶,先到先得典型问题:护航效应(长作业阻塞短作业)2.短作业优先(SJF)原理:执行时间短的优先,分抢占/非抢占问题:长作业可能饿死生活场景:快餐店小份餐优先出餐,大胃王套餐最后做3.优先级调度原理:按优先级执行,分静态/动态优先级生活场景:医院急诊分级(濒危>危重>急症>非急症)数字越小优先级越高进程 | 到达 | 执行 | 优先级 P1 | 0ms | 8ms | 3 P2 | 1ms | 4ms | 1 ← 最高 P3 | 2ms | 9ms | 4 抢占式执行 0----1----5----14 |P1 |P2 |P1 |P34.时间片轮转(RR)原理:每个人轮流吃一口,看起来都很忙生活场景:自助餐每人限时5分钟,轮流用餐时间片=5ms执行示例:进程 | 到达 | 执行 P1 | 0ms | 13ms P2 | 1ms | 4ms P3 | 2ms | 6ms 0---5---9---15---18 |P1 |P2 |P3 |P15.多级队列调度原理:按性质分多个队列,不同队列不同策略生活场景:火车站分窗口:军人/急客窗口(优先级+RR)普通窗口(FCFS)团购窗口(批处理)前台队列(交互式):RR调度,时间片=8ms 后台队列(批处理):FCFS,时间片=32ms 系统队列(内核):优先级最高 队列规则:进程固定属于某队列队列间按优先级调度队列内用各自算法6.多级反馈队列(MFQ)原理:RR + 优先级 + 队列升级(Linux实际使用)生活场景:餐厅会员体系:新客→普通座(Q0)超时用餐→升包厢(Q1)长期用餐→升VIP(Q2)Q0(优先级最高):RR时间片=8msQ1:RR时间片=16msQ2(最低):FCFS时间片=32ms核心规则:新进程进入Q0用完时间片未结束 → 降级到下一队列等待过久未执行 → 提升到上级队列高优先级队列非空时,不执行低优先级{/card-default}{card-default label="核心计算模板" width=""}P1: 到达=0, 执行=7 P2: 到达=2, 执行=4 P3: 到达=4, 执行=1 0-3: P1 → 3-6: P2 → 6-7: P3 → 7-10: P2 → 10-14: P1 进程 | 到达 | 服务 | 完成 | 周转 | 带权 | 等待 P1 | 0 | 7 | 14 | 14 | 2.0 | 0+(10-3)=7 P2 | 2 | 4 | 10 | 8 | 2.0 | (3-2)+(7-6)=2 P3 | 4 | 1 | 7 | 3 | 3.0 | 6-4=2 计算平均值 平均等待 = (7+2+2)/3 = 3.67ms 平均周转 = (14+8+3)/3 = 8.33ms 平均带权 = (2+2+3)/3 = 2.33陷阱1:抢占式vs非抢占式混淆错误:题目说"抢占式SJF",却按非抢占计算正确:SRTF要在每个新进程到达时重新选择最短剩余时间P1(执行10ms)运行中,P2(执行1ms)到达 → 立即抢占P1执行P2 ✅陷阱2:时间片轮转切换开销忽略时间片=5ms,切换开销=1ms P1(0-4)运行 → P2(5-9)运行 → 实际耗时不是5ms而是6ms!陷阱3:带权周转时间算错执行时间=3ms,周转=24ms 带权 = 24/3 = 8 ✅ 不是 3/24 = 0.125 ❌陷阱4:响应时间理解错误错误:认为响应时间=结束时间正确:响应时间=首次运行时间-到达时间(第一次被调度)陷阱5:多级队列饥饿问题低优先级队列进程永远不执行 → 解决方案:优先级老化(等待越久优先级越高)陷阱6:护航效应识别题目:长作业+多个短作业用FCFS后果:短作业等待时间巨长 → 解决方案:用SJF或RR{/card-default}现实操作系统应用Linux CFS调度器核心理念:完全公平调度(Completely Fair Scheduler)关键技术:红黑树管理进程,按虚拟运行时间vruntime排序计算公式:vruntime = 实际时间 × (NICE_0_LOAD / 权重)调度周期:所有进程至少运行一次的时间生活类比:班级轮流发言,说话时间短的人优先Windows调度器32级优先级:0-31,0为最低动态调整:前台窗口线程+2,I/O操作后+1实时优先级:16-31级(需管理员权限实时系统调度硬实时 必须准时完成 最早截止时间优先(EDF)软实时 偶尔超时允许 速率单调调度(RMS)案例:导弹控制→EDF算法;视频播放器→RMS算法进程调度就是CPU的"交通警察":FCFS是排队,SJF是绿色通道,RR是轮流执政,多级反馈是智能交通系统,最终目标——让所有"车辆"都满意!{card-default label="进程资源图" width=""}分配边:进程与具体某个已持有实例的关系。(历史:已满足的请求)申请边:进程与整个资源类的关系。(当前:一个新的、未满足的请求)在进程资源图中,箭头是描述进程和资源之间关系的关键元素,它清晰地表达了资源的申请和分配状态。简单来说,箭头从谁指向谁,就表示“谁需要谁”或“谁等待谁”。申请边(Request Edge)方向:从 进程 指向 资源类(方框)。涵义:表示该进程正在申请(等待)该资源类中的一个实例,但尚未得到。状态:进程因此被阻塞,处于等待状态。口语化理解:“进程想要一个这种资源,但还没拿到。”分配边(Assignment Edge) / 持有边(Allocation Edge)方向:从 资源类中的某个具体实例(方框中的圆点)指向 进程。涵义:表示该资源类中的一个具体实例已经分配给了这个进程,由该进程持有。状态:进程正占用着此资源。口语化理解:“这个资源实例已经在进程手里了。如果资源有空闲,请求会被瞬间满足,申请边在快照中就不会出现(它会被立即转换为一条分配边)。因此,在静态资源图分析中,默认图中出现的每一条申请边都对应一个被阻塞的进程。假设资源有三个能分配的资源,但是已经有三个分配箭头了,这时还有一个申请边,那就是阻塞关于b图:R2的3个资源已经占用了2个,当进程P1和P3请求占用R2的时候,无论分配给哪一方都可以使两个进程都满足所需的资源,从而可以化简,P2也可得所需的R1资源。因此P1和P3是非阻塞节点。疑问:“申请边存在不是意味着阻塞吗?”是的,在静态快照中,画出的申请边意味着该进程在快照时刻因该请求而处于阻塞状态。但是,在动态化简分析中,我们关心的是这种阻塞是否是“永久性的”。如果系统当前有足够的空闲资源能立即(或在其他进程释放资源后)满足该请求,那么这个阻塞就是暂时的、可解除的。这样的进程在化简中就被归为“非阻塞节点”。“非阻塞节点”的真实含义:更准确的说法是“可立即满足或可被化简的节点”。它不是指进程在快照时刻未阻塞,而是指从当前系统状态出发,该进程的请求能够在不引起死锁的情况下被满足。{/card-default}动态分区内存管理中的空闲分区合并(或合并自由块)在动态分区分配方式中,当进程运行结束释放内存时,系统会将其占用的分区回收,并检查该分区是否与相邻的空闲分区相邻(地址连续)。如果回收区与上方和下方的空闲区都相邻,则系统会将这三个分区合并为一个更大的空闲分区,从而导致空闲区总数减少1。此过程旨在减少外部碎片,提高内存利用率。要保证系统中 6 个并发进程(每个进程都需要 2 个互斥资源 R)不发生死锁,资源 R 的最少数目为 7。推导过程:在最坏情况下,每个进程都已获得 1 个资源,同时还需要 1 个资源才能继续执行。此时如果系统中只剩 1 个可用资源,则可分配给任意一个进程,使其满足全部资源需求并运行完毕,之后释放的资源又能供其他进程使用,从而避免死锁。因此,最少资源数 = 进程数 × (每个进程所需资源数 - 1) + 1 = 6 × (2 - 1) + 1 = 7。
2026年02月04日
5 阅读
0 评论
0 点赞
1
2
3
...
36
0:00