操作系统中锁变量与转弯变量的区别2025年1月7日 | 阅读13分钟 在本文中,我们将讨论操作系统中锁变量 (Lock Variable) 和轮转变量 (Turn Variable) 之间的区别。但在讨论区别之前,我们必须了解操作系统中的锁变量和轮转变量。 什么是锁变量?在操作系统和并发编程领域,锁变量 (Lock variable),有时也称为锁 (Lock),是一种同步原语,用于管理多线程或多进程环境中对共享资源的访问。锁变量的作用是防止多个线程或进程同时访问代码的关键区域或共享数据结构。锁变量还可以消除竞态条件或数据损坏等情况。 锁变量通常有两种状态:锁定 (Locked) 和解锁 (Unlocked)。当一个线程或进程获取锁时,表示该线程或进程有权访问关键区域。当一个线程或进程获取锁时,任何试图访问关键区域的其他进程都必须等到前一个进程释放该锁。 锁变量如何用于同步?锁被用作一种有效的同步机制,以实现互斥,其目的是只允许一个进程(或)线程进入代码的关键区域。在它们完成自己的回合并且程序执行顺序确定之前,它不允许访问共享资源。 为了使用锁变量进行同步,线程或进程通常遵循以下步骤: - 获取锁:线程在进入代码的关键区域之前或在以共享方式处理资源之前,会先获取锁。如果锁当前是未锁定的,它将被锁定,然后线程或进程继续执行该关键区域。
- 执行关键区域:锁是一种特殊代码,旨在应对多个线程或进程对共享资源的竞争。因此,一旦线程获取锁(一次只允许一个线程),它就可以安全地运行代码的关键区域或对共享资源执行操作,而不会发生冲突。
- 释放锁:在关键区域完成并且线程或进程释放锁后,锁会恢复到其原始的未锁定状态。这样做之后,其他线程或进程就可以获取锁来访问共享资源。
实现细节锁变量可以使用各种技术实现,例如: - 互斥锁 (Mutexes):互斥锁 (Mutex) 也用作锁变量,有助于分配共享资源。因此,它们常见于许多低级操作系统原语,包括原子指令和系统调用,通过这些原语提供对锁的线程安全访问。
- 自旋锁 (Spinlocks):自旋锁是一种忙等待同步方法,其中线程在持有锁时会间歇性地查询锁变量。然而,自旋锁运行速度很快,但当它们被长时间持有并且 CPU 闲置时,可能会占用 CPU 资源。
- 基于信号量的锁:信号量是同步变量的选择之一,可用于实现锁变量。具有初始值为 1 的二元信号量可以用作锁,每次只允许一个线程片段进入关键区域。
锁变量的优缺点使用锁变量进行同步的优点包括: - 简单性:锁变量提供了一种限制对共享资源的访问的实用方法,但这并不使理解并发代码中的情况变得更容易。
- 通用性:锁可以应用于任何共享资源,从简单的变量到复杂的数据结构。
- 鲁棒性:它是一种防止竞态条件并确保多线程或多进程环境中数据完整性的方法,因此基于锁的同步得到了恰当的处理。
然而,基于锁的同步也有一些缺点: - 死锁的可能性:如果进程未能按预期释放锁并等待锁,则可能出现死锁,因为也可能出现一个线程一直等待另一个线程持有的锁的情况。
- 性能开销:在上述情况下,我们需要维护或替换锁,这会导致高上下文切换问题以及原子操作,这些操作会影响程序性能,尤其是在竞争场景更可能发生的情况下。
- 优先级反转的风险:在抢占式优先级调度方案中,低优先级线程通常会争夺锁,并且可能持有它们,从而阻止高优先级线程取得更多进展。因此,存在优先级反转的可能性。优先级较低的线程也可能导致系统流程变慢,因为资源密集型的较慢线程通常会阻塞较快的单核机器。
什么是轮转变量?在操作系统和多线程编程环境中,轮转变量 (Turn variable) 是一种标志或令牌,充当并发控制的同步原语,为线程或进程提供对共享资源的访问。其基本功能在于根据顺序限制对关键区域或共享资源的访问。它可以使所有进程平等地访问关键区域或共享资源。 轮转变量如何用于同步?轮转变量通常应用于关键区域(如代码段或共享资源)以及争夺访问权的多个线程和进程。通过为每个线程和每个进程分配唯一的标识符来标记它们,并按照分配的顺序序列模拟它们的访问顺序来完成此过程。 使用轮转变量进行同步的基本步骤如下: - 初始化:队列或任务的每个部分在初始化时都会被标记一个通行证号码。该标识符用于按顺序确定哪个进程可以选择器可以使用关键区域(或)资源。
- 请求访问:线程或进程仅在满足有关轮转变量的规则时才进入关键区域,即当它们的编号与当前编号相同时。它要么处于空闲/等待状态,要么,如果不是它的回合,它将被推迟直到轮到它。
- 授予访问:接着,成为关键区域或资源持有者的线程或进程获得控制权,并对对象执行操作,直到它成为资源的拥有者,然后释放控制权。
- 更新轮转变量:完成其指令后,线程会与轮转变量通信并将其增加 1,以告知将选择下一个线程或进程。
实现细节轮转变量可以使用各种技术实现: - 原子操作:原子操作可以分配硬件或操作系统提供的变量。原子更新操作确保变量的更改是原子执行的,因此是线程安全的。
- 软件实现:整数变量或标志,用于表示当前轮次或票证编号,可以截断软件对应的实现。为避免竞态条件,需要特别注意协调地址指针的变化。
轮转变量的优缺点使用轮转变量进行同步的优点包括: - 公平性:轮转变量可以通过强制执行严格的进程顺序来访问资源,从而有助于确保竞争线程/进程之间的公平资源分配。
- 简单性:基于轮次的同步具有一些可能更复杂的特性,因此需要大量的推理和实现工作来协助进行更操作密集的同步任务。
- 避免死锁:与某些基于锁的同步机制相比,轮转变量通常不会导致死锁,因为它们以先入先出的顺序授予访问权。
然而,基于轮次的同步也有一些缺点: - 饥饿的可能性:如果低优先级线程或进程不断将机会让给高优先级线程或进程,那么它将花费太长时间才能获得其 CPU 时间,从而导致饥饿,这在资源分配中是不公平的。
- 开销:如果存在大量线程和进程,使用基于轮次的同步方法进行同步会减慢整个系统的速度,因为每次访问共享变量都会产生开销。
- 可扩展性有限:通过将共享变量转换为信号量来协调不同线程或进程之间的共享资源,可能无法保证快速同步。
锁变量和轮转变量的区别锁变量和轮转变量之间有几个主要区别,如下所示: 同步机制- 锁变量:锁变量、互斥对象允许任何时候只有一个线程或进程对关键区域或资源进行独占访问。线程只能在使用锁,并在必要时释放锁才能获得访问权。
- 轮转变量:通过轮转变量,强制执行一个定义的规则,关于多个线程或进程访问共享资源的顺序,这通常通过分配的标识符或令牌来编号。最终,当所有线程都加入后,它们将按顺序一个接一个地进行。
控制粒度- 锁变量:锁在非常细的粒度上起作用,针对特定标记为受保护的部分,开发人员可以指定这些受保护的代码段。这使得通过联合资源可以同时访问不同的部分,从而鼓励高效地利用资源。
- 轮转变量:基于轮次的同步通常在更粗粒度上强制执行,它强制执行全局的轮询服务。每个线程要么存储在就绪队列中,要么等待其回合执行相关的代码关键区域,从而存在资源争用的风险并相应地降低并发级别。
阻塞行为- 锁变量:当一个线程试图获取另一个线程拥有的锁时,它会被置于阻塞状态,直到锁被释放。在这种共识下,可以通知死锁以快速预防和处理问题。
- 轮转变量:等待访问资源的线程通常不像锁那样阻塞。相反,它们可能会忙等待或让出 CPU 直到轮到它们。这可以减少死锁的可能性,但可能会带来额外的开销。
对资源类型的适应性- 锁变量:锁可用于保护内存中的各种资源,例如数据结构、代码段和硬件设备。它提供了精确的控制级别,允许将单个资源独占分配给第一个需要使用的资源。
- 轮转变量:启发式同步,通过优先级或资源转移来体现,更适合资源访问按步骤进行的情况,例如连续任务或在特定数量的实体之间分配资源。它可以选择性地覆盖某些资源和逻辑代码行,但不能覆盖其他资源和代码行,从而在其保护方面更加精确。
抢占处理- 锁变量:依赖于锁的同步机制可能需要处理抢占场景,即高优先级线程或中断抢占了持有锁的线程。有效处理抢占是避免死锁或数据损坏的重要标准之一。
- 轮转变量:基于轮次的同步不会导致抢占问题,因为每个线程现在都将等待,直到轮到该特定线程访问资源。这使得在抢占式多任务环境中重新调度此类会话更加简单,因为抢占持有锁的线程可能会带来限制。
实现复杂度- 锁变量:采用基于锁的同步技术并非总是容易的,尤其是在我们需要实现嵌套锁、递归锁场景或多重锁时。
- 轮转变量:实现基于轮次的同步在概念上与同步步骤和决定回合顺序的联合变量的背后概念几乎相同。然而,公平性和警告例如饥饿情况等挑战可能会在思考如何准确解决这些问题时出现。
等待策略- 锁变量:等待锁的线程可以采用多种等待策略。它可以是自旋(也称为忙等待)、休眠或将 CPU 让给其他线程。等待策略的选择可能会提高或降低性能,并直接影响资源使用。
- 轮转变量:基于轮次的同步通常具有简单的机制,例如提供一个平台,让线程或进程可以“等待”或“让出”给彼此。在进行这种获取时,其设计比基于锁的同步开销更小,尤其是在频繁拥塞的情况下。
中断处理- 锁变量:在中断可能发生在关键区域操作中的系统中,通过基于锁的同步机制退出关键区域的执行可能是可取的。中断可能需要特殊指令来满足严格的固件条件并避免竞态条件。
- 轮转变量:通过被动基于轮次的同步方法进行同步可能对外部中断源免疫,例如当每个线程都处于开启状态,并且仅当所有其他线程都在等待自己的回合时才允许其运行。然而,为了确保公平性和避免饥饿,尤其是在具有严格时序要求的系统中,仍然可能需要中断处理。
分布式系统中的可扩展性- 锁变量:基于锁的同步在分布式系统中可能会遇到问题,因为锁需要在不同节点之间进行,并且可能涉及多个进程。网络延迟、一个节点中的死锁检测和一致性等问题可能导致锁管理中的各种困难。
- 轮转变量:同步对称性是网络系统中可扩展的分布,因为每个节点或进程都可以通过全局接受的回合顺序通知。通过这种方式,可以保持互操作性的简单性,并且分布式锁定机制范例将不再复杂。
容错性和弹性- 锁变量:基于锁的同步机制引起的死锁可能导致系统脆弱,因为存在单点故障。在集中式系统架构中,当锁仅由一个实体管理时,更容易遭受此类故障。
- 轮转变量:基于轮次的同步可以在分布式系统中提供改进的容错性,因为轮转变量可以复制或分布在多个节点上。这种冗余有助于减少故障的影响并提高系统弹性。
开销- 锁变量:性能问题通常源于锁定和解锁等操作,这些操作通常通过系统调用或原子执行来实现,从而带来了上下文切换、同步原语和缓存未命中的开销。如果锁也受到严重争用,它们可能会成为额外的受管理资源,从而增加开销。
- 轮转变量:在具有大量线程的系统中,基于轮次的同步可能会产生开销,因为每次访问共享资源都需要检查和更新轮转变量。随着竞争线程数量的增加以及访问共享资源的频率,这种开销也会增加。
公平性- 锁变量:基于锁的同步不能固有地保证公平的资源分配。如果线程更频繁地获取锁或持有锁的时间更长,它们可能会遭受饥饿或不公平的待遇。
- 轮转变量:基于轮次的同步通过要求特定的共享资源访问顺序来促进公平性。所有线程都确保按照轮转变量指定的顺序访问资源,从而消除了饥饿并提供了公平的资源分配。
资源利用效率- 锁变量:在线程/进程频繁获取锁的情况下,使用锁进行资源同步可能导致资源浪费。这种情况很可能导致多个线程空闲或被阻塞在处理之前,从而导致 CPU 资源利用不足。
- 轮转变量:在某些情况下,基于轮次的同步可以提高资源消耗效率,因为线程或进程在等待访问资源的回合时可以继续执行。这可以导致更一致的 CPU 消耗和更高的整体系统性能。
对无阻塞算法的支持- 锁变量:最常用的基于锁的同步方法利用阻塞或重复的忙等待来管理有关资源保护的问题。无锁算法,即使在竞争情况下也能取得进展,很难在非阻塞版本中实现。
- 轮转变量:可以按回合进行的同步更可能适合无阻塞算法,因为每个线程或进程都在等待自己的回合,而不会锁定或无限期等待。轮次有利于设计和实现可扩展的无同步同步算法。
对任务依赖图的适用性- 锁变量:在具有各种任务依赖关系和丰富任务图或工作流的并发系统中,基于锁的同步方法可能会难以准确地定义这些任务依赖关系。协调具有许多相互依赖任务的复杂系统中的共享资源访问可能是一项艰巨的任务,因为这样的系统可能会面临死锁和资源利用效率低下。
- 轮转变量:基于轮次的同步可用于同步具有任务依赖关系的任务系统中的资源访问,因为线程或进程可以根据任务依赖关系以指定的顺序执行。这可以使资源协调更容易,并提高整体系统效率。
易于调试和诊断- 锁变量:关于与基于锁的同步相关的复杂性,应指出线程的顺序是随机的,资源同时被并发线程争用。可能会遇到各种困难,例如死锁、竞态条件或优先级反转。可以通过使用复杂的算法和分析工具来检测同步错误更深地隐藏在代码级别。
- 轮转变量:基于轮次的同步确保了由确定性轮转变量决定的资源访问的统一性。轮次可以使开发人员和测试人员映射访问顺序,从而使他们能够检测潜在的错误。这可以简化在程序中定位同步问题,并有助于构建更可靠的并发软件。
与硬件事务的互操作性- 锁变量:虽然基本的同步机制与 HTM(硬件事务内存)系统不完全兼容,HTM 通常使用投机执行来进行事务性代码块,而这是同步最简单的实现。这可能导致事务失败或系统运行缓慢。
- 轮转变量:基于轮次的同步可能更适合 HTM 系统,因为它强制执行清晰的资源访问顺序,而不依赖于低级锁定原语。这种兼容性可以更有效地利用并发程序中的硬件事务内存。
|