本书的原著为:《Design Patterns for Embedded Systems in C ——An Embedded Software Engineering Toolkit 》,讲解的是嵌入式系统设计模式,是一本不可多得的好书。
本系列描述我对书中内容的理解。本文章描述嵌入式并发和资源管理模式之一:基本并发概念。
基本概念
大多数嵌入式系统需要同时执行多个任务。实现这一点的关键在于定义和管理系统的并发模型。要理解并发,需要先理解并发的基本术语和概念:
动作序列
(Action Sequence):一个任务
包含一个或多个动作
,这些动作共同协作,组成动作序列
,以完成特定的目标或任务。动作序列中可能包含决策分支点,但执行顺序是完全确定的。并发单元
(Concurrency unit):也称为任务
。本质是在一个独立的执行线程中顺序执行的一组动作。并发单元
是并发系统中的基本构建块。在并发编程中,将相关的动作组合成一个并发单元有助于管理和调度。每个并发单元通常具有明确的开始和结束,以及一组要执行的动作或指令。这些动作在执行过程中可能会访问共享资源、进行输入/输出操作、进行计算等。到达模式
(Arrival Pattern):在并发系统中,到达模式
是一个重要概念,用于描述 触发并发单元
(如任务、进程)开始执行的事件的到达方式。这些事件可以是外部输入、内部状态变化、定时器超时、消息到达等。到达模式可以分为两大类:周期性的(Periodic)和非周期性的(Aperiodic)。
阻塞
(Blocking):在并发系统中,阻塞
是一个常见的现象,它指的是一个进程因为等待某个条件(如资源、消息等)而停止执行的情况。在阻塞状态下,进程或线程不会消耗CPU资源,但它也无法进行任何计算或执行任何操作,直到它所等待的条件得到满足。阻塞时间
(Blocking time):特指一个高优先级任务因为等待一个低优先级任务释放所需资源而被阻止运行的时间长度。并发
(Concurrency):指在同一时刻有多个动作序列执行。截止时间
(Deadline):在并发系统中,截止时间
指的是一个特定的时间点,超过这个时间点,某个动作或动作序列的完成将变得不正确或功能失效。对于具有截止时间的动作或动作序列,系统必须确保在截止时间之前完成这些动作,以满足系统的时间约束和正确性要求。关键性
(Criticality):指的是一个动作序列完成的重要性。在并发系统中,不同的动作序列可能具有不同的关键性级别,这取决于它们对系统整体功能、性能或安全性的影响。关键性高的动作序列通常被认为是系统中的核心或关键任务,它们的及时完成对于系统的正常运行至关重要。这些任务可能需要安排更高优先级,因此,
关键性
是确定任务优先级的关键因素之一。紧迫性
(Urgency): 指的是一个动作序列截止时间的临近程度。越接近截止时间,任务的紧迫性越高。紧迫性也是确定任务优先级的关键因素。及时性
(Timeliness):任务在截止时间之前可预测地完成,或满足其与时间相关的性能要求的能力。死锁
(Deadlock):是一种特定的系统状态,在这种状态下,多个进程无限期地等待一个或多个资源,而这些资源又被其他等待进程持有。由于每个进程都在等待其他进程释放资源,而没有一个进程愿意主动放弃它已经持有的资源,因此系统陷入了一种僵持状态,无法自行恢复。死锁通常发生在以下四个条件同时满足时,这被称为死锁的四个必要条件( Coffman 条件):
1.互斥条件:至少有一个资源必须处于非共享模式,即一次只有一个进程能够使用。如果其他进程请求该资源,请求进程只能等待,直到资源被释放。
2. 持有并等待条件:一个进程至少持有一个资源,并且正在等待获取其他进程持有的额外资源。
3. 不可抢占条件:资源不能被强制从一个进程中夺走,它必须被持有它的进程显式地释放。
4. 循环等待条件:存在一个进程等待循环,即 P1 等待 P2 持有的资源,P2 等待 P3 持有的资源,…,Pn 等待 P1 持有的资源。
当这四个条件同时成立时,死锁就有可能发生。为了避免死锁,系统设计者通常会采用一些策略,如死锁预防(通过破坏上述四个条件中的至少一个来防止死锁发生)、死锁避免(通过资源分配算法来确保系统不会进入死锁状态)、死锁检测与恢复(允许系统进入死锁状态,但能够检测到这种状态并采取措施恢复)。执行时间
(Execution time):指的是完成一个动作或动作序列所需的时间长度。在并发系统中,执行时间是一个重要的性能指标,它直接影响系统的吞吐量和响应时间。到达间隔时间
(Interarrival time):指的是非周期性任务两次连续启动执行之间的时间间隔。对于非周期性任务,触发任务开始执行的事件是不规则的,因此到达间隔时间
也是变化的。了解和预测到达间隔时间对于优化系统的性能和资源利用率至关重要。到达
(Arrival):即到达模式
中讲述的那个“到达”。指的是触发任务开始执行的事件变得有效,因此称为该事件“到达”了,此时任务会开始执行。周期
(Period):指的是周期性任务两次连续调用之间的时间间隔。抖动
(Jitter):是指周期性任务的实际执行周期与其标称周期之间的偏差或变化。在实时系统和并发环境中,抖动是一个重要的性能指标,因为它可以影响系统的可预测性和稳定性。抖动可能由多种因素引起,包括处理器负载变化、操作系统调度策略、中断服务例程的执行、资源共享冲突以及外部干扰等。多任务抢占式调度
(Multitasking Preemptive Scheduling):多任务抢占式调度
是一种调度策略,其中优先级最高的就绪任务将被选择执行,如果当前有较低优先级的任务正在运行,则该任务将被抢占(即中断其执行),以便优先级更高的任务可以立即开始执行。优先级
(Priority):优先级在并发系统中是一个关键概念,它用于确定在多个就绪任务中哪个任务先获得执行的机会。每个任务都被赋予一个优先级值,这个值通常是一个数字,用来反映任务的重要性和紧急性。优先级反转
(Priority inversion):一个较低优先级的任务由于某种原因(通常是资源占用)而阻塞了较高优先级任务的执行,导致系统的整体行为不符合基于优先级的预期调度。优先级反转例子:较低优先级的任务持有了某个共享资源,而这个资源是较高优先级任务所需要的。由于资源被低优先级任务占用,高优先级任务不得不等待低优先级任务释放资源,这种高优先级任务不能抢占低优先级任务的现象,称为优先级反转。
伪并发
(Pseudoconcurrency):计算机科学术语,指在单处理器上,通过快速的任务切换来模拟出多个任务并发执行的假象。尽管在物理层面上,同一时刻只能有一个任务在执行,但操作系统可以通过多任务抢占式调度或其他调度策略,使得多个任务看起来像是在同时运行。竞争状态
(Race Condition):如果输出的结果依赖于不受控制的事件的出现顺序或执行的时间长短,那么我们便称发生了竞争状态。通常,当两个或多个进程在没有适当同步的情况下,访问共享数据或资源,并且至少有一个进程在修改这些数据或资源时,最终的结果将取决于这些进程的执行顺序。由于进程调度的不确定性,这可能导致不可预测和不可重复的结果。竞争状态通常是由于缺乏适当的同步机制(如互斥锁、信号量等)来确保对共享数据的互斥访问而引起的。实时动作
(Real-Time):需要在特定时间窗口内完成,以满足系统正确性要求的动作。如果实时动作不能在规定的时间内完成,那么即使动作本身在功能上是正确的,也可能导致整个系统的失败或不符合预期的行为。可调度性
(Schedulability):如果一个任务集中的所有任务都能按时完成,那么这个任务集就被称为是可调度的。同步模式
(Synchronization Pattern):同步模式规定了任务在何时、何地以及如何进行同步。这对于防止竞态条件、确保数据一致性以及优化系统性能至关重要。常见的同步模式包括:信号量、互斥量等。同步点
(Synchronization Point):任务的动作序列中的一个特定动作,任务会在此等待,直到其他任务也达到相应的同步点。同步点是并发编程中的一个关键概念,它用于确保任务间的协同和一致性。当多个任务需要共同协作完成某个任务时,它们必须在某些关键点上同步,以确保每个任务都按照预期的顺序和条件进行。同步点可以是一个锁的获取、一个信号量的等待、一个条件变量的通知,或者是任何其他形式的同步机制。
任务
(Task):语义同 “并发单元”。线程
(Thread):语义同 “并发单元”。最坏情况执行时间
(Worst Case Execution Time):指的是动作序列执行所需的最长时间。
任务的动作
动作
、动作序列
和 任务
的关系如下图所示:
每个任务(也称为并发单元)由一组按照特定顺序执行的动作组成。这些动作序列是独立于其他序列执行的。这意味着在一个任务内部,动作执行的顺序是完全已知的;比如图中的任务 1 ,首先执行动作 A,其次执行动作 B,再执行动作 C 或者动作 D,再执行动作 E,最后执行动作 F。但在不同任务之间,哪个动作先执行是不确定的。比如图中任务 1~3 中的动作哪个先执行?是动作 A?动作 3?还是动作 II?答案是你不知道、也不关心!如果在并发设计中关心这种顺序,那么并发对你而言不是一个好的选择。
在并发系统中,你唯一关心任务间执行顺序的点称为同步点(synchronization points)。在上图中,这些同步点通过一对水平条来表示。第一个水平条称为“汇合”(join),因为它在逻辑上将来自不同任务的一组动作汇合成一个单一的线程。第二个水平条称为“分叉”(fork),因为它从这个单一线程分叉成多个线程。
任务间通讯
任务之间的通信可以通过两种基本方式实现:同步通信和异步通信。
-
同步通信
类似于电话通话,双方同时参与,立即调用服务并交换数据。发送方“发送并等待”接收方的响应。同步通信通常通过函数调用实现。在这种通信模型中,发送方在发送消息后会等待接收方的响应,才能进行下一步操作。这种等待可能会阻塞发送方,直到接收方处理完消息并返回结果。同步通信的优点是简单、直观,易于理解和调试;缺点是可能会导致发送方长时间等待,降低系统的并发性和效率。 -
异步通信
则类似于寄送明信片,发送方在发送消息后会立即返回并继续执行其他操作,而不会等待接收方的响应。接收方会在稍后的某个时间点接收到消息并进行处理。因此,异步通信中的消息处理可能不够及时,但发送方无需等待接收方准备好就可以继续执行其他任务。异步通信的实现方式有很多种,例如通过消息队列、事件驱动等方式实现。这种通信模型的优点是可以提高系统的并发性和效率,因为发送方无需等待接收方的响应就可以继续执行其他操作;缺点是实现起来可能比较复杂,需要额外的机制来确保消息的可靠传输和处理。
任务并发
为了让任务 (真正) 并发执行,它们必须在多核 CPU 的不同 CPU 或核心上运行。在给定的 CPU 核心内,多个任务是通过 伪并发
来运行的,这意味着在任何给定的瞬间只有一个任务在执行。然而,处理器通过在就绪任务之间切换,给人一种并发的假象。
值得注意的是,
并发
和并行
是两个不同的概念。
并发
是指在一个时间段内有多个任务在进行,但不一定同时开始或结束。并行
则是指多个任务在同一时刻同时进行。在多核处理器系统中,可以实现真正的并行处理,因为每个核心都可以独立地执行一个任务。
任务的调度方式
任务的基本结构在很大程度上受到其调度模式的影响。对于顺序循环执行的任务,它们通常表现为一系列按顺序执行的代码,一旦完成即结束。然而,在协作多任务环境中,任务往往以循环的方式运行,但在某些关键点主动放弃控制权,以便调度器可以安排其他任务执行。
如下所示的代码清单展示了一个典型的循环执行体示例。在这个例子中,首先声明了全局数据,并进行了系统初始化。如果初始化成功,程序将进入一个无限循环,其中包含了任务的主要执行逻辑。
void main(void)
{
/* 全部变量*/
static int nTasks = 3;
int currentTask;
/* 初始化代码 */
currentTask = 0;
if (POST())
{
/* 调度执行 */
while (TRUE)
{
task1();
task2();
task3();
};
}
}
不同的调度方式在任务执行中各有利弊:
顺序循环调度器
方式最为简单,其调度器由单一的无限循环构成,按顺序逐个执行任务。然而,这种方式的灵活性较差,对于实时事件的响应能力可能不足。因为任务必须等待,直到轮到该任务运行。这可能导致事件处理延迟。此外,为了确保其他任务能够及时运行,每个任务的执行时间必须尽量简短。时间触发循环调度器
是顺序循环调度的一种变体,它在固定的时间边界开始循环。如果在下一个时间边界之前完成了循环,它会进入休眠状态,直到下一个周期开始。这种方式提高了对时间的控制精度,但仍然可能受到任务执行时间长短的限制。协作式轮询调度器
则采用了一种不同的策略。它基本上也是一个顺序循环,但任务不会一直运行到完成。相反,它们会在特定的点主动将控制权释放给调度器。这种方法允许长时间运行的任务分阶段执行,同时确保其他任务也有机会获得处理资源。这种调度方式提高了系统的并发性和响应能力,但也需要更复杂的任务设计和同步机制。
虽然顺序循环调度器和协作式轮询调度器都很简单,但它们都存在一些问题:
- 首先,它们无法对紧急事件做出响应;处理紧急事件的任务只是按照其顺序运行。
- 其次,它们不够灵活,因为调度顺序是在设计时确定的。
- 第三,它们无法很好地扩展到大量任务。
- 最后,一个行为不当的任务会阻止整个系统的执行。
这些类型的调度器适合于非常简单的系统或高度安全关键的系统(因为简单的系统比复杂的系统更容易证明正确性和进行认证)。
另一种主要的调度方式是 抢占式调度
。在抢占式调度中,调度器有权在必要时中断当前正在执行的任务,转而运行下一个任务。决定何时抢占当前任务以及如何选择下一个任务的标准,取决于所采用的特定调度策略。
在高层次上,抢占式调度器中的任务可以划分为四种核心状态:
- 当任务运行所需的条件尚未满足时,该任务处于
等待状态
。 - 一旦任务运行所需的所有条件都已满足,该任务便进入
就绪状态
,随时准备被执行。 - 当调度器实际启动任务时,该任务则处于
运行状态
。 - 如果任务已准备好运行,但由于其他任务占用了必要的资源而无法执行,那么该任务就会进入
阻塞状态
。
时间片调度器
(time-driven multitasking executive,TDME)以公平原则为基础运作,确保每个任务都有均等的执行机会。在这种调度方式下,任务在运行特定的时间段后会被抢占,以确保其他任务也有机会执行。时间片的大小是这种调度器调优的关键参数。如果时间片设置得过长,可能会导致其他任务无法及时获得执行机会,从而影响系统的响应性和并发性。相反,如果时间片设置得过短,任务上下文切换所占用的时间比例就会显著增加,从而降低系统的整体效率。
对于较大的嵌入式系统来说,最常见的调度模式是 基于优先级的调度
。在这种方法中,每个任务都被分配一个优先级。优先级通常是一个数值,用于确定在多个就绪任务中哪个任务先获得执行的机会。
在时间片或基于优先级的抢占式调度器中,任务以无限循环的方式运行,并在其他任务需要执行时被调度器中断。下面给出的代码展示了基于优先级的抢占式调度器的基本结构:
/* 私有静态数据*/
static int taskAInvocationCounter = 0;
void taskA(void)
{
/* 任务私有数据*/
int stufftoDo;
/* 初始化代码 */
stuffToDo = 1;
while (stuffToDo)
{
signal = waitOnSignal();
switch (signal)
{
case signal1:
/* 信号 1 处理 */
break;
case signal2:
/* 信号 2 处理 */
case signal3:
/* 信号 3 处理 */
};
};
};
从代码中可以看到任务静态数据和堆栈数据的位置。在这些数据声明之后,通常会有一些初始化或设置代码。接下来是一个无限循环(或者至少是一个不确定的循环),在这个循环中,任务会不断地等待感兴趣的信号,这些信号可能来自于数据更新或事件发生。当接收到这样的信号时,使用 switch case 语句进行相应的处理。处理完成后,任务代码会返回到循环的起始处,继续等待下一个信号的到来。
任务的优先级
任务的优先级可以根据任何可能的标准来设置,但基于优先级的调度主要有两种类型:紧迫性调度
和 关键性调度
。很多人不准确地使用这些术语,但这些术语有精确的含义:
紧迫性
指的是任务截止时间的临近程度。在紧迫性调度中,任务的优先级是根据其截止时间的临近程度来确定的。截止时间越近,任务的优先级就越高。这种调度策略适用于那些有严格时间约束的任务。关键性
指的是任务执行对系统的重要程度。关键性调度更注重任务执行对系统整体性能或稳定性的影响。在这种调度策略中,即使任务的截止时间并不紧迫,但如果它的执行对系统的正常运行至关重要,那么它也会被赋予较高的优先级。
下面的图片显示了这两者之间的差异。
曲线本身被称为 效用函数
;效用函数是一个用于衡量任务完成对系统价值的函数。
关键性可以理解为效用曲线的高度,而紧迫性则是到达关键性降为零的时间距离。值得注意的是,实时任务并不一定都有截止时间,但截止时间是指定实时任务 及时性
约束最常见的方式。
值得注意的是,紧迫性和关键性并不是完全独立的概念。在某些情况下,一个任务可能同时具有紧迫性和关键性。
在任务执行时,有几个与时间相关的重要术语,如下图所示:
- 周期性任务:以固定速率执行的任务
- 周期:任务两次连续调用之间的时间间隔
- 抖动:周期性任务的实际执行周期与其标称周期之间的偏差或变化
- 非周期性任务
- 到达间隔时间:非周期性任务两次连续启动执行之间的时间间隔
- 最小到达间隔时间:最短的时间
- 执行时间:完成任务活动所需的时间,在计算可调度性 (任务集中的所有任务都能按时完成) 时,通常使用最坏情况下的执行时间。
- 截止时间:指任务准备好运行到必须完成之间的时间段。
优先级反转
当你使用信号量来锁定资源时,可能会遇到一种情况,即一个低优先级的任务首先获得了一个资源并锁定了它,但随后被一个高优先级的任务抢占。然而,这个高优先级的任务也需要访问这个已经被锁定的资源。此时高优先级任务必须进入阻塞状态,等待低优先级的任务释放资源。这种情况被称为 优先级反转
,因为它违反了正常的优先级调度规则,即高优先级的任务应该优先于低优先级的任务运行。
需要注意的是,在使用信号量保护资源的多任务抢占式调度中,优先级反转是无法避免的。这并不一定是坏事,但 不受控制的优先级反转
几乎总是坏事。考虑如下图所示的内容:
在这个图中,有两个任务。
- 任务 A :优先级较高的任务,执行周期为 50 ms,执行时间为 10 ms,其中 5 ms 锁定资源 R。
- 任务 Z :优先级较低的任务,执行周期为 1000 ms,执行时间为 500 ms,其中 10 ms 锁定资源 R。
请注意,在此示例中,每个任务的截止时间都发生在下一个周期的开始时,这意味着任务 A 的截止时间为 50 ms,任务 Z 的截止时间为 1000 ms。
如果任务 Z 在任务 A 需要资源 R 时持续占用它,这就会导致不受控制的优先级反转。因为任务 A 的优先级更高,它应该能够更频繁地访问资源 R,但由于任务 Z 的持续占用,任务 A 可能会被无限期地阻塞。这种情况下,系统的实时性能可能会受到严重影响,因为高优先级的任务无法及时完成。
当任务 Z 正在运行并锁定了资源 R,而此时任务 A 变得可以运行时。任务 A 会立即抢占任务 Z,但一旦任务 A 到达需要访问资源 R 的点,它就会被“卡住”;任务 A 必须阻塞并允许任务 Z 运行,直到任务 Z 释放资源,就会发生优先级反转。一旦任务 Z 释放了资源,任务 A 再次抢占任务 Z,一切恢复正常。
如果你关注任务的 可调度性
(几乎总是应该关注),那么你应该检查阻塞对可调度性的影响。
术语
可调度性
,是指一个任务集中的所有任务都能按时完成。
在上图的案例中,最坏的情况是什么?它发生在任务 Z 刚刚锁定 R,然后任务 A 运行。在这种情况下,任务 A 的总执行时间是 10 ms 加上阻塞时间 10 ms,任务 A 在 20 ms 内完成(少于截止时间)。而任务 Z 的执行时间是 500ms + 10 次任务 A 的运行(每次 10 ms)= 600 ms,(也少于截止时间),所以虽然发生了优先级反转,但结果还不差,每个人都很快乐。
现在,考虑如下图所示的情况:
在加入了任务 Y 和任务 X 后,我们需要重新评估系统的 可调度性
。
- 任务 Y :次低优先级任务,执行周期 800 ms ,执行时间为 100 ms。
- 任务 X :第三低优先级的任务,执行周期为 500 ms,执行时间为 80 ms。
这些新任务并不直接使用资源 R,但它们会影响任务 A 和任务 Z 的执行。最坏情况如下所示:
- 任务 Z 首先运行并锁定了资源 R。
- 任务 A 开始运行,任务 A 优先级更高,它立即抢占任务 Z。任务 A 运行到需要访问资源 R 的时候,因得不到资源 R 而被阻塞。
- 此时,任务 Z 继续运行
- 但紧接着任务 Y 开始运行,由于任务 Y 优先级高于任务 Z,因此它立即抢占任务 Z。
- 在任务 Y 运行期间,任务 X 变得可以运行,并且由于任务 X 的优先级高于任务 Y,因此它立即抢占任务 Y。
这样,任务 A 被三个低优先级的任务(Z、Y 和 X)所阻塞,无法访问所需的资源 R。
由于任务 Y 和任务 X 的执行,最坏情况下任务 A 需要 190 ms ( 任务 Z 锁定资源 10 ms + 任务 Y 执行时间 100 ms + 任务 X 执行时间 80 ms)才能获得资源 R 在总共190ms。这导致了任务 A 错过其截止时间,因为它无法在 50ms 的周期内完成任务。这种现象称为 不受控制的优先级反转
,是一个严重的问题,因为它可能导致高优先级任务无法及时完成,从而影响系统的实时性能和可靠性。
为了解决 不受控制的优先级反转
问题,目前已经有多种设计解决方案。其中一些最常见的策略包括使用 临界区
和 优先级继承
模式。
临界区(Critical Regions):
临界区是一种机制,保护对共享资源的访问,确保在给定时间内只有一个任务可以访问该资源。通过限制对资源的并发访问,临界区可以减少优先级反转的可能性。优先级继承(Priority Inheritance):
优先级继承是一种策略,当低优先级任务持有高优先级任务所需的资源时,低优先级任务将暂时继承高优先级任务的优先级。这样做可以确保低优先级任务不会被其他中等优先级的任务抢占,从而减少了高优先级任务的等待时间。一旦低优先级任务释放了资源,它就恢复到原来的优先级。
任务划分
之前关于任务及其属性的讨论都回避了一个关键问题,即针对特定的嵌入式系统,如何划分任务。一般而言,良好的任务应具有独立的动作序列,仅在特定的交互点与其他任务进行交互。然而,仅仅满足这些条件并不足以构成一个优秀的任务集,因为我们的目标是优化系统的性能和响应能力。原则上,我们可以将每个对象(或函数)运行在一个单独的任务线程中,也可以将它们全部集中在一个单一的任务线程中。
如果易于维护是首要考虑的因素,那么 独立性
应该是划分任务的主要策略。如果可调度性是首要考虑的因素,那么 复现属性
(例如任务周期)应该是划分任务的主要策略。如下所示的表格展示了常见的任务划分策略。
复现属性(Recurrence Properties):
在计算机科学和嵌入式系统设计中,“recurrence properties” 通常指的是任务或事件定期重复出现的特性。这些属性可能包括任务的执行周期、任务的截止时间以及任务之间的相对优先级等。
策略 | 描述 | 优点 | 缺点 |
---|---|---|---|
单一事件 | 为每个任务使用单一事件类型。这意味着每个任务都被设计为响应或处理一种特定类型的事件。 | 简单,因为每个任务只需要关注一种类型的事件,并且可以更容易地预测和管理任务的行为。 | 不能用于复杂场景; 不能达到最佳性能 |
中断处理 | 使用单一事件类型触发中断 | 对于处理一小组输入事件类型来说实现简单; 在处理紧急事件方面效率很高 | 不适用于处理大量事件; 中断处理程序通常必须非常短且是原子的; 可能会丢失事件; 难以与其他任务共享数据 |
事件源 | 将来自单一源的所有事件分组,以便由一个任务处理 | 简单 | 不能用于复杂场景; 不能达到最佳性能 |
关联信息 | 将信息的所有处理(通常围绕资源)组合到一个任务中 | 适用于后台任务,这些任务不具有紧迫性 | 不能用于复杂场景; 不能达到最佳性能 |
独立处理 | 将没有依赖关系的动作序列封装为任务 | 简化任务 | 可能导致线程数量过多,不能达到最佳性能; 没有解决资源共享或任务同步的问题 |
接口设备 | 一种事件源 | 处理接口设备(如:总线)很有用 | 同“事件源” |
复现属性 | 将具有相似重复属性的事件分组在一起,例如周期、最小到达间隔时间或截止时间 | 最适用于可调度性; 可以及时响应事件,从而实现最佳调度 | 比较复杂 |
安全保证 | 将特殊的安全和可靠性保证行为组合在一起(例如,看门狗) | 对于安全关键和高可靠性系统来说,这是增加安全性和可靠性处理的好方法 | 不能达到最佳性能 |
并发和资源管理的设计模式
- 循环执行模式:一种常见的设计模式,用于管理多个周期性或非周期性的任务。在这种模式中,一个无限循环(通常称为主循环或事件循环)负责按照预定的顺序和时间间隔调用不同的任务或线程。
- 静态优先级模式:一种线程调度策略,其中每个线程都被分配一个静态(即不变的)优先级。在这种模式下,调度器总是选择优先级最高的线程来执行。
- 临界区模式:在并发编程中,临界区模式是一种用于保护共享资源不被多个任务或线程同时访问的技术。当一个任务进入临界区时,它会暂时禁用任务切换,以确保在其执行关键代码段期间,不会有其他任务打断它的执行并访问相同的共享资源。
- 保护性调用模式:在并发编程中用于保护共享资源的常见策略。它通过使用互斥信号量来确保对共享资源的访问是互斥的,即同一时间只有一个任务或线程可以访问这些资源。
- 排队模式:一种在并发编程和分布式系统中常见的设计模式,用于实现任务、事件或数据之间的序列化访问和通信。在此模式中,发送者将消息发送到队列,而接收者则从队列中接收并处理这些消息。队列作为一个中介,负责存储和转发消息,同时保证消息的有序性和一致性。
- 会合模式:用于协调复杂任务同步的设计模式。在这种模式中,多个任务或进程在特定的点(称为会合点”)进行会合,以交换信息、协调行动或同步状态。这种模式有助于确保任务之间的正确协作和顺序执行。
- 同时锁定模式:一种用于避免死锁的并发编程策略。在这种模式中,当多个线程或进程需要同时访问多个共享资源时,它们会尝试同时获取所有这些资源所需的锁。如果无法同时获得所有锁,线程将放弃尝试,并可能稍后重试或执行其他操作,而不是持有部分锁并等待其他锁变得可用,这种情况可能会导致死锁。
- 有序锁定模式:一种预防死锁的并发编程策略,它要求线程或进程在请求多个锁时,必须按照预先定义好的顺序来获取这些锁。这种模式通过消除循环等待条件来避免死锁,循环等待是死锁发生的四个必要条件之一。
这其中:
循环执行模式
和静态优先级模式
属于调度模式
范畴。临界区模式
、保护性调用模式
、排队模式
和会合模式
属于任务协作模式
范畴。同时锁定模式
和有序锁定模式
属于避免死锁模式
范畴。
- 调度模式 (Scheduling Patterns):在软件设计中,调度模式是对任务、进程或线程进行调度时所采用的一种或多种特定的策略。这些模式旨在优化资源利用率、提高系统性能、确保实时响应等。
- 任务协作模式 (Task Coordination Patterns):在软件设计中,协作模式是用于协调不同任务之间通讯和同步的策略。它旨在确保任务能够高效、有序地执行,并处理任务之间的依赖关系、优先级冲突和资源共享等问题。
- 避免死锁模式 (Deadlock Avoidance Patterns):在并发编程中,避免死锁模式是为了防止死锁(Deadlock)的发生而采用的一系列设计策略。
在接下来的文章中,会依次描述这些设计模式。
读后有收获,资助博主养娃 - 千金难买知识,但可以买好多奶粉 (〃‘▽’〃)