深入理解Linux内核中的同步与互斥的实现

1. 内联汇编

汇编函数的执行效率比C语言更高,但可移植性,可编程性和可读性更差,掌握也更复杂。所以一般使用C语言编程。

1.1 内联汇编的优点

  1. 性能优化:内联汇编允许开发者利用底层硬件特性,编写出更高效的代码,尤其是在性能敏感的场景下。

  2. 直接硬件控制:内联汇编可以直接对硬件寄存器和内存地址进行操作,这在实现某些硬件驱动或直接与硬件交互的功能时非常有用。

  3. 原子操作:在多线程编程中,内联汇编可以执行原子操作,保证数据的一致性和同步。

  4. 特定系统调用:在Linux内核编程中,内联汇编常用于实现系统调用,因为它可以精确控制函数的输入输出和寄存器状态。

  5. 减少抽象层:在某些情况下,直接使用汇编可以减少C语言抽象层带来的性能开销。

  6. 利用CPU特性:可以利用特定CPU的优化指令,比如SSE、AVX(SSE(Streaming SIMD Extensions)和AVX(Advanced Vector Extensions)是两种由Intel和AMD共同开发的CPU指令集扩展,它们主要用于提高处理器在多媒体应用和浮点运算方面的性能)等,以实现更高效的数据处理。

1.2 内联汇编的缺点

  1. 可移植性差:内联汇编与特定的CPU架构紧密相关,导致代码的可移植性降低。

  2. 可读性和维护性:混合使用C和汇编语言会降低代码的可读性,增加维护难度。

  3. 调试难度:内联汇编的存在可能会使调试变得更加困难,特别是在跟踪问题时需要理解汇编层面的执行细节。

  4. 编译器优化限制:内联汇编的存在可能会限制编译器的优化能力,因为编译器可能无法对汇编代码进行有效的优化。

  5. 代码膨胀:如果过度使用内联汇编,可能会导致生成的二进制文件体积增大,尤其是在多处使用相同汇编代码的情况下。

  6. 安全风险:如果不正确地使用内联汇编,可能会引入安全漏洞,比如缓冲区溢出等。

  7. 学习曲线:对于不熟悉汇编语言的开发者,编写和理解内联汇编代码需要额外的学习成本。

  8. 版本依赖:不同版本的编译器对内联汇编的支持程度可能不同,这可能会导致代码在不同编译器或版本间出现兼容性问题。

 1.3 C语言中的内联汇编和C++中的内联函数

C语言的内联汇编和C++中的内联函数虽然都使用了inline关键字,但它们在概念和用途上存在明显的区别:

  1. C语言中的内联汇编: 内联汇编是C语言中一种特定的特性,它允许在C代码中嵌入汇编语言指令。这种技术通常用于需要高性能或者访问硬件级别的编程中。内联汇编通过asm关键字实现,并且可以包含输入输出操作数以及被修改的寄存器列表,提供了一种灵活的方式来执行底层硬件操作。

  2. C++中的内联函数: 内联函数是C++中用于提高程序执行效率的一种机制。当编译器处理内联函数时,它会在调用点展开函数体,从而减少函数调用的开销。内联函数通常用于那些小而频繁调用的函数。在C++中,内联函数可以通过将inline关键字放在函数定义前声明。

两者的主要区别在于:

  • 用途:内联汇编用于嵌入汇编代码,实现底层或高性能的硬件操作;内联函数用于提高C++程序的执行效率,通过减少函数调用开销。
  • 实现:内联汇编通过asm关键字和相应的语法规则嵌入汇编指令;内联函数通过inline关键字在C++中声明。
  • 控制:内联汇编提供了对汇编代码的精确控制,包括寄存器使用和指令执行;内联函数的展开由编译器控制,开发者只能提出建议。
  • 语言绑定:内联汇编通常与特定的硬件架构相关,而内联函数是C++语言的一部分,与硬件无关。

1.4 内联汇编的语法

内联汇编语法:

asm(表明身份)

也可以写作“__asm__”,表示这是一段内联汇编。

asm-qualifiers(加以限制)

有3个取值:volatile、inline、goto。

volatile的意思是易变的、不稳定的,用来告诉编译器不要随便优化这段代码,否则可能出问题。比如汇编指令“mov  r0, r0”,它把r0的值复制到r0,并没有实际做什么事情,你的本意可能是用这条指令来延时。编译器看到这指令后,可能就把它去掉了。加上volatile的话,编译器就不会擅自优化。

AssemblerTemplate(汇编指令)

,用双引号包含起来,每条指令用“\n”分开,比如:

“mov  %0, %1\n”

“add  %0, %1, %2\n”

OutputOperands(结果位置)

输出操作数,内联汇编执行时,输出的结果保存在哪里。

格式如下,当有多个变量时,用逗号隔开:

[ [asmSymbolicName] ] constraint (cvariablename)

asmSymbolicName是符号名,随便取,也可以不写。

constraint表示约束,例如读写,加减等操作。

cvariablename:C语言的变量名。

InputOperands(输入位置)

输入操作数,内联汇编执行前,输入的数据保存在哪里。

格式如下,当有多个变量时,用逗号隔开:

[ [asmSymbolicName] ] constraint (cexpression)

asmSymbolicName是符号名,随便取,也可以不写。

constraint表示约束,作用如上

cexpression:C语言的表达式。

示例1如下:

Clobbers(中间变量)

(在汇编代码中,对于“OutputOperands”所涉及的寄存器、内存,肯定是做了修改。但是汇编代码中,也许要修改的寄存器、内存会更多。比如在计算过程中可能要用到r3保存临时结果,我们必须在“Clobbers”中声明r3会被修改。

举例:内联汇编实现两数相加。

代码:把第1、2个操作数相加,存入第0个操作数。也就是把a、b相加,存入sum。

2. 何谓同步和互斥

2.1 同步和互斥的概念

概述:同步用于实现多任务之间的协调,不发生冲突,类比于通信之间的同步,通信双方之间约定的顺序来收发数据,系统中同步就类比于多方通信,互斥则是独占,我干了其他人不许干。

同步(Synchronization):同步是指在多线程环境中,线程之间通过某种机制来协调它们对共享资源的访问顺序。同步机制确保在任一时刻,只有一个线程可以访问特定的资源。同步的目的是在保证数据一致性的同时,允许适当的并发性。Linux中常见的同步机制包括:

  • 互斥锁(Mutex):提供互斥访问,一次只允许一个线程持有锁并访问资源。
  • 信号量(Semaphore):可以看作是计数的互斥锁,允许多个线程访问同一资源,直到达到设定的计数限制。
  • 读写锁(Read-Write Lock):允许多个线程同时读取资源,但在写入时需要独占访问。
  • 条件变量(Condition Variable):允许线程在某些条件不满足时挂起等待,直到其他线程更改条件并通知它们。
  • 屏障(Barrier):一组线程在继续执行之前等待所有线程到达某个点。

互斥(Mutual Exclusion):互斥是同步的一种形式,专注于确保多个线程不会同时访问某个特定的资源。互斥机制通常通过锁来实现,确保在任一时刻只有一个线程可以执行关键区段的代码。互斥的重要性在于防止:

  • 数据竞争(Race Condition):当两个或多个线程访问同一数据,而它们的执行顺序影响结果时发生的情况。
  • 不一致状态(Inconsistent State):如果多个线程同时修改数据,可能会导致数据处于不一致的状态。

Linux中实现互斥的方法包括:

  • 互斥锁(Mutexes):通过锁定和解锁机制,确保一次只有一个线程可以进入关键区段。
  • 自旋锁(Spinlocks):一种忙等待锁,适用于中断处理和高速缓存敏感的环境。
  • 读写锁(RWLocks):允许多个读者同时访问资源,但写者需要独占访问。

2.2 互斥与同步的区别

虽然互斥和同步在概念上密切相关,但它们之间存在细微差别:

  • 互斥侧重于防止多个线程同时访问同一资源,通常通过锁机制实现。
  • 同步则是一个更广泛的概念,它不仅包括互斥,还包括线程间的协调机制,如条件变量和屏障,它们可以协调线程间的执行顺序,而不一定直接限制对资源的访问。

2.3 同步和互斥的必要性

程序A在调用驱动程序的中途被程序B抢占了CPU资源:

程序A执行到第11行之前,被程序B抢占了,这时valid尚未被改成0;

程序B调用gpio_key_drv_open时,发现valid等于1,所以成功返回0;

当程序A继续从第11行执行时,它最终也成功返回0;

这样程序A、B都成功打开了驱动程序。

对于单CPU核的系统可以采用关闭中断的方法防止自己的代码呗其他应用程序打断;但是对于SMP系统,你只能关闭当前CPU核的中断,别的CPU核还可以运行程序,它们也可以来执行这个函数,同样导致问题,如下图:

解决此问题的方法:原子操作,锁等。

3. 原子操作 

概述:Linux有2种原子操作:原子变量、原子位。原子操作在cpu系统和SMP(Symmetric Multi-Processors,对称多处理)系统的实现方式不一致,对于cpu系统简单粗暴,关中断。对于SMP系统,在ARMv6及以上的架构中,有ldrex、strex指令,ex表示exclude,意为独占地。这2条指令要配合使用,读出:ldrex  r0, [r1]读取r1所指内存的数据,存入r0;并且标记r1所指内存为“独占访问”。如果有其他程序再次执行“ldrex  r0, [r1]”,一样会成功,一样会标记r1所指内存为“独占访问”。 修改r0的值,写入:strex  r2, r0, [r1]:如果r1的“独占访问”标记还存在,则把r0的新值写入r1所指内存,并且清除“独占访问”的标记,把r2设为0表示成功。如果r1的“独占访问”标记不存在了,就不会更新内存,并且把r2设为1表示失败。

综上所述:在ARMv6及以上的架构中,原子操作的执行过程是可以被打断的,但是它的效果符合“原子”的定义:一个完整的“读、修改、写入”原子的,不会被别的程序打断。它的思路很简单:如果被别的程序打断了,那就重来,最后总会成功的。

3.1 原子操作的关键特点

定义:原子操作(Atomic Operation)是指在多线程环境中,一个操作在执行过程中不会被其他线程中断,要么全部完成,要么全部不执行。这种操作是不可分割的最小执行单元,可以保证在并发环境下数据的一致性和完整性。

  1. 不可中断性:原子操作一旦开始执行,就会连续执行直到完成,中间不会受到其他线程的干扰。

  2. 一致性:原子操作保证了操作的开始和结束状态是一致的,不会出现中间状态暴露给其他线程的情况。

  3. 无需额外同步:原子操作本身是线程安全的,不需要使用互斥锁或其他同步机制来保证其执行。

3.2 原子操作的应用场景

  1. 计数器更新:在多线程程序中更新计数器时,使用原子操作可以避免多个线程同时修改同一个变量导致的数据不一致问题。

  2. 资源分配:在分配或释放资源时,如内存管理、连接池等,使用原子操作可以确保资源的正确分配和回收。

  3. 状态标记:在标记状态变化时,如设置或清除标志位,原子操作可以保证状态的一致性。

  4. 锁机制:在实现自旋锁等锁机制时,原子操作可以用来安全地获取和释放锁。

  5. 硬件访问:在访问某些硬件寄存器时,使用原子操作可以保证对硬件状态的一致性访问。

3.3 原子操作的优点

  • 性能:原子操作通常比使用互斥锁具有更高的性能,因为它们不需要进入和退出内核态,也不需要进行线程切换。

  • 简单性:原子操作的使用比复杂的同步机制更简单,可以减少编程错误。

  • 安全性:原子操作保证了操作的原子性,减少了死锁和竞态条件的风险。

然而,原子操作也有局限性,它们通常只适用于简单的数据类型和操作。对于复杂的数据结构或操作,可能需要使用其他同步机制来保证线程安全。

4. Linux锁的介绍与使用 

4.1 锁的类型

自旋锁(Spinning Lock):

自旋锁是一种忙等待(busy-waiting)的锁机制。当一个线程尝试获取一个已被其他线程持有的自旋锁时,它不会立即阻塞(即不会进入睡眠状态),而是在当前位置不断循环检查锁是否已被释放。这种方式称为“自旋”,因为线程在锁被释放前会持续“旋转”或循环检查。

特点

  • 忙等待:线程在锁被持有时不会睡眠,而是不断检查锁的状态。
  • 低延迟:适用于锁持有时间短且线程不希望在锁释放前让出CPU的情况。
  • 适用于中断处理:在中断上下文中通常不允许睡眠,自旋锁是首选。
  • CPU资源消耗:如果锁被长时间持有,自旋锁会导致CPU资源的浪费。

睡眠锁(Sleeping Lock):

睡眠锁通常指的是那些在无法立即获得时会让线程进入睡眠状态的锁,如互斥锁(mutex)。当一个线程尝试获取一个已被其他线程持有的睡眠锁时,它会进入睡眠状态,直到锁被释放并被唤醒。

特点

  • 线程睡眠:当线程无法获取锁时,它会被放入睡眠状态,直到锁被释放。
  • 适用于长持有时间:当锁可能被长时间持有时,使用睡眠锁可以避免浪费CPU资源。
  • 可能引起线程切换:线程在睡眠时可能会引起操作系统进行线程切换。
  • 可能涉及上下文切换开销:唤醒线程并将其调度到运行状态可能涉及上下文切换的开销。

对比:

  • CPU使用:自旋锁可能在锁竞争激烈时消耗更多CPU资源,而睡眠锁则不会。
  • 响应性:自旋锁可以提供更快的响应性,因为线程不需要经过睡眠和唤醒的过程。
  • 适用场景:自旋锁适用于锁持有时间短且竞争激烈的环境,而睡眠锁适用于锁持有时间较长的环境。

4.2 信号量和互斥量

信号量(Semaphore)和互斥量(Mutex)都是用于多线程同步的机制,但它们的设计目的和使用方式有所不同。

信号量(Semaphore):信号量是一种计数器,它是一种更通用的同步工具,可以用来控制多个线程对共享资源的访问。信号量可以看作是一个计数器,每当一个线程获得资源时,计数器减一;当线程释放资源时,计数器加一。

特点

  • 计数信号量:信号量的值可以是正数、零或负数,表示可用资源的数量。
  • 多个资源:信号量可以用来管理多个相同类型的资源。
  • 多个线程:允许多个线程同时访问资源,只要信号量的值大于零。
  • P/V操作:信号量的操作包括P操作(等待或减量,Proberen的缩写,荷兰语“测试”的意思)和V操作(释放或加量,Verhogen的缩写,荷兰语“增加”的意思)。

互斥量(Mutex):互斥量是一种用于保护共享资源的同步机制,确保一次只有一个线程可以访问资源。互斥量通常用于实现临界区的互斥访问。

特点

  • 锁/解锁:互斥量通过锁定和解锁操作来控制对共享资源的访问。
  • 二进制信号量:互斥量可以看作是一个初始值为1的信号量,当一个线程获得锁后,计数器变为0,其他线程必须等待。
  • 防止竞争条件:互斥量用于防止多个线程同时进入临界区,从而避免数据竞争。
  • 简单易用:互斥量的使用通常比信号量更简单,因为它只涉及锁定和解锁两个操作。

信号量与互斥量的区别:

  1. 计数方式:信号量是一个计数器,可以表示多个资源;互斥量通常是一个二进制锁,表示单个资源。
  2. 用途:信号量可以用于更复杂的同步场景,如多个生产者和消费者问题;互斥量通常用于保护临界区,防止多个线程同时访问。
  3. 性能:在某些情况下,互斥量的性能可能优于信号量,因为它通常由操作系统直接支持。
  4. 使用场景:互斥量更适合于锁的竞争不激烈且锁持有时间较短的场景;信号量适用于需要精细控制资源访问数量的场景。

在实际编程中,选择信号量还是互斥量取决于具体的应用需求和场景。例如,在POSIX线程(pthread)库中,sem_t用于创建和管理信号量,而pthread_mutex_t用于创建和管理互斥量。正确使用这些同步机制对于构建稳定、高效且无死锁的多线程应用程序至关重要。

5. 自旋锁

概述:自旋锁是针对SMP系统多cpu抢占同一资源的一种同步机制,对于单cpu系统只存在禁止抢断和禁止中断功能。

定义:自旋锁(Spinlock)是一种用于多线程同步的锁机制,其特点是在等待锁的过程中,线程不会释放CPU,而是在当前位置不断循环(自旋),检查锁是否已被释放。这种锁适用于锁持有时间短且系统上下文切换开销较大的场景。

5.1 自旋锁的特点

  1. 忙等待(Busy-Waiting):当一个线程尝试获取一个已被其他线程持有的自旋锁时,它不会进入睡眠状态,而是在当前位置循环检查锁是否被释放。

  2. 非阻塞:自旋锁不会使线程进入阻塞状态,因此避免了线程从运行状态到就绪状态的切换开销。

  3. 适用于锁持有时间短的场景:如果锁被持有的时间很短,使用自旋锁可以减少线程从运行状态到就绪状态再到运行状态的切换开销。

  4. CPU资源消耗:如果锁被长时间持有,自旋锁可能导致CPU资源浪费,因为等待锁的线程会不断消耗CPU周期。

  5. 避免线程切换:由于线程不会进入睡眠状态,自旋锁避免了线程切换的开销,这在中断处理程序或实时系统中尤为重要。

  6. 实现简单:自旋锁的实现通常比互斥锁简单,因为它不需要操作系统的支持来挂起和恢复线程。

  7. 死锁风险:由于自旋锁不会使线程睡眠,因此在某些情况下可能增加死锁的风险,因为一个线程可能无限期地等待一个不会被释放的锁。

5.2 自旋锁的使用场景和实现

使用场景:

  • 中断处理:在中断上下文中,通常不允许线程睡眠,自旋锁是管理中断处理程序中共享资源访问的首选。
  • 高性能计算:在高性能计算场景中,锁持有时间短,上下文切换开销高,自旋锁可以提供更好的性能。
  • 实时系统:在实时系统中,自旋锁可以确保线程在等待资源时不会错过关键的时限。

实现:

在Linux内核中,自旋锁可以通过spinlock_t类型实现,其基本操作包括:

  • spin_lock_init(&lock):初始化自旋锁。
  • spin_lock(&lock):获取自旋锁,如果锁已被其他线程持有,则进入忙等待。
  • spin_unlock(&lock):释放自旋锁。

注意事项:

  • 在使用自旋锁时,需要确保锁的持有时间尽可能短,以减少CPU资源的浪费。
  • 需要避免在可能长时间持有锁的代码段中使用自旋锁,以防止CPU资源浪费和其他线程饥饿。
  • 在设计系统时,应仔细考虑自旋锁的使用,以避免死锁和性能问题。

5.3 在SMP系统中自旋锁的实现 

6. 信号量semaphore的实现

信号量(Semaphore)是一种用于控制多个进程或线程对共享资源访问的同步机制。信号量的实现通常依赖于操作系统的支持,以确保原子性和线程安全。

6.1 信号量的实现过程

1. 定义信号量:

信号量是一个整数变量,它可以有正数、零或负数的值。信号量通常初始化为正数,表示可用的资源数量。

2. P操作(Proberen,测试):

P操作用于请求资源。如果信号量的值大于零,它将减少信号量的值,然后继续执行。如果信号量的值为零或负数,调用P操作的进程或线程将被阻塞,直到信号量的值变为正数。

3. V操作(Verhogen,增加):

V操作用于释放资源。它将信号量的值增加1,如果有进程或线程因为信号量的值为零或负数而被阻塞,那么它们将被唤醒,并且其中一个将尝试执行P操作。

 

4. 原子操作:

信号量的操作需要是原子的,以避免多个进程或线程同时修改信号量值。原子操作可以通过锁或其他同步机制实现。

5. 阻塞和唤醒机制:

当信号量的值为负数时,需要有一种机制来阻塞请求资源的进程或线程。同样,当信号量增加时,需要唤醒等待的进程或线程。

 

6. 同步原语:

为了防止多个进程或线程同时修改信号量,可以使用互斥锁或其他同步原语来保护信号量的操作。

注意事项:

  • 信号量的操作应该是无锁的或使用其他同步机制来避免竞争条件。
  • 信号量的设计应该避免优先级反转问题,这可能需要使用优先级继承等技术。
  • 在使用信号量时,应避免死锁和资源泄露。

信号量的实现可以根据不同操作系统的API和语言特性进行调整,但基本原理相同。在POSIX标准中,信号量可以通过sem_t类型和相关函数如sem_init(), sem_wait(), sem_post(), sem_destroy()来实现。

6.2 semaphore的内核实现代码分析

信号量的定义及操作函数都在Linux内核文件include\linux\semaphore.h中定义,如下:

初始化semaphore之后,就可以使用down函数或其他衍生版本来获取信号量,使用up函数释放信号量。我们只分析down、up函数的实现。

 down函数的实现:

如果semaphore中的count大于0,那么down函数就可以获得信号量;否则就休眠。在读取、修改count时,要使用spinlock来实现互斥。休眠时,要把当前进程放在semaphore的wait_list链表中,别的进程释放信号量时去wait_list中把进程取出、唤醒。代码如下:

 up函数的实现:

如果有其他进程在等待信号量,则count值无需调整,直接取出第1个等待信号量的进程,把信号量给它,共把它唤醒。如果没有其他进程在等待信号量,则调整count。整个过程需要使用spinlock来保护,代码如下:

7. 互斥量mutex的实现

互斥量(Mutex)是一种用于多线程同步的锁机制,用于保护共享资源不被多个线程同时访问。以下是互斥量的一般实现原理:

7.1 互斥量的实现过程

1. 定义互斥量结构:

互斥量通常由一个锁状态标志(通常是一个整型变量)和可能的其他辅助信息(如拥有锁的线程ID、等待队列等)组成。

 

2. 初始化互斥量:

在创建互斥量时,需要将其初始化为未锁定状态。

 

3. 锁定互斥量(Lock):

当线程需要访问共享资源时,它必须首先尝试锁定互斥量。如果互斥量未被锁定,线程将成功锁定它并继续执行。如果互斥量已被其他线程锁定,请求锁定的线程将被阻塞。

 

4. 解锁互斥量(Unlock):

当线程完成对共享资源的访问后,它必须解锁互斥量,以便其他线程可以访问该资源。解锁操作将互斥量的锁定状态重置为未锁定,并可能唤醒等待锁的线程。

 

5. 原子操作:

互斥量的锁定和解锁操作需要是原子的,以避免多个线程同时修改锁状态。原子操作可以通过锁或其他同步机制实现。

 

6. 阻塞和唤醒机制:

当互斥量已被锁定时,需要有一种机制来阻塞请求锁定的线程。同样,当互斥量被解锁时,需要唤醒等待的线程。

 

注意事项:

  • 互斥量的设计应该避免死锁,确保总有线程能够获得锁。
  • 应避免在持有互斥量时进行长时间操作或调用可能阻塞的函数。
  • 在某些系统中,持有互斥量的线程可能被优先调度,以减少优先级反转的风险。

7.2 互斥量的内核实现代码分析

mutex的定义及操作函数都在Linux内核文件include\linux\mutex.h中定义,如下:

7.2.1 fastpath

mutex的设计非常精巧,比semaphore复杂,但是更高效。

首先要知道mutex的操作函数中有fastpath、slowpath两条路径(快速、慢速):如果fastpath成功,就不必使用slowpath。

怎么理解?

这需要把metex中的count值再扩展一下,之前说它只有1、0两个取值,1表示unlocked,0表示locked,还有一类值“负数”表示“locked,并且可能有其他程序在等待”。

代码如下:

大部分情况下,mutex当前值都是1,所以通过fastpath函数可以非常快速地获得mutex。

快处理(Fast Path)

快处理是指当锁可以被迅速、无争用地获取时所采用的路径。在快处理中,线程尝试以非阻塞的方式获得锁,通常涉及以下步骤:

  1. 尝试锁定:线程尝试通过原子操作快速地将锁的状态从“未锁定”变为“锁定”。
  2. 成功获取:如果锁当前没有被其他线程持有,线程成功获取锁,并立即进入临界区。
  3. 避免阻塞:快处理避免了线程阻塞和调度开销,适用于锁争用低的场景。
7.2.2 slowpath

慢处理(Slow Path)

慢处理是指当锁已经被其他线程持有,当前线程需要等待锁被释放时所采用的路径。在慢处理中,线程可能需要执行以下操作:

  1. 等待锁:线程发现锁已被持有,因此将其自身放入等待队列或睡眠状态。
  2. 可能的阻塞:线程可能被操作系统挂起,直到锁被释放并被唤醒。
  3. 锁释放后获取:当锁被释放时,等待的线程将被唤醒并尝试重新获取锁。

如果mutex当前值是0或负数,则需要调用__mutex_lock_slowpath慢慢处理:可能会休眠等待。核心函数:

其中最主要的流程如下,利用原子操作或者其他同步操作对count的值进行增加或者减少。

在互斥量(Mutex)的使用和实现中,"快处理"(Fast Path)和"慢处理"(Slow Path)是两种不同的处理策略,用于优化锁的获取和释放过程:

快慢处理的对比:

  • 性能:快处理通常性能更高,因为它避免了线程的阻塞和上下文切换的开销。
  • 适用场景:快处理适用于锁争用不高的情况,而慢处理适用于锁争用高且需要等待的场景。
  • 实现复杂度:快处理通常实现简单,而慢处理可能需要更复杂的逻辑来管理等待队列和线程唤醒。

示例:

在实现互斥量时,可能会使用自旋锁作为快处理的一部分,因为自旋锁在锁被快速释放的情况下非常高效。然而,如果锁被长时间持有,自旋锁可能导致CPU资源浪费,此时慢处理(如使用阻塞锁)可能更为合适。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值