Linux中的各种锁及其基本原理

深入理解RCU无锁的核心原理 其中很多实现都可以说是非常精巧,结合了预处理,批量处理,延后(异步)处理,多核并发,原子操作,异常处理,多场景精细优化等多种技术,性能好,可扩展性强,稳定性强,有一定的学习和参考价值,即使你的工作不是内核编程,里面体现很多编程思想和代码设计思想,也是值得大家学习的。 阅读详情

Linux中的各种锁及其基本原理

1.概述

通过本文将了解到如下内容:

  • Linux系统的并行性特征
  • 互斥和同步机制
  • Linux中常用锁的基本特性
  • 互斥锁和条件变量

2.Linux的并行性特征

Linux作为典型的多用户、多任务、抢占式内核调度的操作系统,为了提高并行处理能力,无论在内核层面还是在用户层面都需要特殊的机制来确保任务的正确性和系统的稳定运行,就如同一个国家需要各种法律条款来约束每个公民的行为,才能有条不紊地运转。

在内核层面涉及到各种软硬件中断、进线程睡眠、抢占式内核调度、多处理器SMP架构等,因此内核在完成自己工作的时候一直在处理这些资源抢占的冲突问题。

在用户层面的进程,虽然Linux作为虚地址模式操作系统,为每个进程开辟了独立的虚拟地址空间,伪独占式拥有资源,但是仍然存在很多场景不得不产生多个进程共享资源的问题,来完成进程间的通信,但是在Go语言中进程间的通信使用消息来完成,处理地更优雅一些。

在线程层面,线程作为进程的一部分,进程内的多个线程只拥有自己的独立堆栈等少量结构,大部分的资源还是过线程共享,因此多线程的资源占用冲突比进程更加明显,所以多线程编程的线程安全问题是个重难点。综上可知,无论在kernel还是user space都必须有一些机制来确保对于资源共享问题的解决,然后这个机制就是接下来要说的:同步和互斥。

3.同步和互斥机制

  • 基本概念

同步和互斥的概念有时候很容易混淆,可以简单地认为同步是更加宏观角度的一种说法,互斥是冲突解决的细节方法。所谓同步就是调度者让任务按照约定的合理的顺序进行,但是当任务之间出现资源竞争,也就是竞态冲突时,使用互斥的规则强制约束允许数量的任务占用资源,从而解决各个竞争状态,实现任务的合理运行。

同步和互斥密不可分,有资料说互斥是一种特殊的同步,对此我不太理解,不过实际中想明白细节就行,文字游戏没有意义。

简单来说:

  • 同步与互斥机制是用于控制多个任务对某些特定资源的访问策略
  • 同步是控制多个任务按照一定的规则或顺序访问某些共享资源
  • 互斥是控制某些共享资源在任意时刻只能允许规定数量的任务访问
  • 角色分类

整个协调流程涉及的角色本质上只有三类:

  • 不可独占的共享资源
  • 多个使用者
  • 调度者

调度者需要为多个运行任务制定访问使用规则来实现稳定运行,这个调度者可以是内核、可以是应用程序,具体场景具体分析。

  • 重要术语

要很好地理解同步和互斥,就必须得搞清楚几个重要术语:

  • 竞争冒险(race hazard)或竞态条件(race condition)

最早听说这个术语是在模电数电的课程上,门电路出现竞态条件造成错误的结果,在计算机里面就是多个使用者同时操作共享的变量造成结果的不确定。

  • 临界区

临界区域critical section是指多使用者可能同时共同操作的那部分代码,比如自加自减操作,多个线程处理时就需要对自加自减进行保护,这段代码就是临界区域。

4.Linux中常用的锁

在说锁之前还需要知道几个东西:信号量和条件变量。这两个东西和锁有一定的联系和区别,在不同的场合单独使用或者配合实现来说实现安全的并发,至于网上很多说互斥锁是一种信号量的特例,对于这种特例理解不了也罢。信号量和互斥锁的场景不一样,信号量主要是资源数量的管理(池化),实际用的频率远不如互斥锁,文字游戏着实无趣,实用主义至上,掌握高频工具的特点正确使用即可,大可不必过于学术派。在使用锁时需要明确几个问题:

  • 锁的所有权问题 谁加锁 谁解锁 解铃还须系铃人
  • 锁的作用就是对临界区资源的读写操作的安全限制
  • 锁是否可以被多个使用者占用(互不影响的使用者对资源的占用)
  • 占用资源的加锁者的释放问题 (锁持有的超时问题)
  • 等待资源的待加锁者的等待问题(如何通知到其他等着资源的使用者)
  • 多个临界区资源锁的循环问题(死锁场景)

带着问题明确想要达到的目的,我们同样可以根据自己的需求设计锁,Linux现有的锁如果从上面几个问题的角度去理解,就非常容易了。

  • 自旋锁spinlock

自旋锁的主要特征是使用者在想要获得临界区执行权限时,如果临界区已经被加锁,那么自旋锁并不会阻塞睡眠,等待系统来主动唤醒,而是原地忙轮询资源是否被释放加锁,自旋就是自我旋转,这个名字还是很形象的。自旋锁有它的优点就是避免了系统的唤醒,自己来执行轮询,如果在临界区的资源代码非常短且是原子的,那么使用起来是非常方便的,避免了各种上下文切换,开销非常小,因此在内核的一些数据结构中自旋锁被广泛的使用。

  • 互斥锁mutex

使用者使用互斥锁时在访问共享资源之前对进行加锁操作,在访问完成之后进行解锁操作,谁加锁谁释放,其他使用者没有释放权限。 加锁后,任何其他试图再次加锁的线程会被阻塞,直到当前进程解锁。 区别于自旋锁,互斥锁无法获取锁时将阻塞睡眠,需要系统来唤醒,可以看出来自旋转自己原地旋转来确定锁被释放了,互斥锁由系统来唤醒,但是现实并不是那么美好的,因为很多业务逻辑系统是不知道的,仍然需要业务线程执行while来轮询是否可以重新加锁。考虑这种情况:解锁时有多个线程阻塞,那么所有该锁上的线程都被编程就绪状态, 第一个变为就绪状态的线程又执行加锁操作,那么其他的线程又会进入等待,对其他线程而言就是虚假唤醒。 在这种方式下,只有一个线程能够访问被互斥锁保护的资源。

  • 读写锁rwlock

读写锁也叫共享互斥锁:读模式共享和写模式互斥,本质上这种非常合理,因为在数据没有被写的前提下,多个使用者读取时完全不需要加锁的。读写锁有读加锁状态、写加锁状态和不加锁状态三种状态,当读写锁在写加锁模式下,任何试图对这个锁进行加锁的线程都会被阻塞,直到写进程对其解锁。

读优先的读写锁:读写锁rwlock默认的也是读优先,也就是:当读写锁在读加锁模式先,任何线程都可以对其进行读加锁操作,但是所有试图进行写加锁操作的线程都会被阻塞,直到所有的读线程都解锁,因此读写锁很适合读次数远远大于写的情况。这种情况需要考虑写饥饿问题,也就是大量的读一直轮不到写,因此需要设置公平的读写策略。在一次面试中曾经问到实现一个写优先级的读写锁,感兴趣的可以想想如何实现。

  • RCU锁

RCU锁是读写锁的扩展版本,简单来说就是支持多读多写同时加锁,多读没什么好说的,但是对于多写同时加锁,还是存在一些技术挑战的。RCU锁翻译为Read Copy Update Lock,读-拷贝-更新 锁。Copy拷贝:写者在访问临界区时,写者将先拷贝一个临界区副本,然后对副本进行修改;Update更新:RCU机制将在在适当时机使用一个回调函数把指向原来临界区的指针重新指向新的被修改的临界区,锁机制中的垃圾收集器负责回调函数的调用。更新时机:没有CPU再去操作这段被RCU保护的临界区后,这段临界区即可回收了,此时回调函数即被调用。从实现逻辑来看,RCU锁在多个写者之间的同步开销还是比较大的,涉及到多份数据拷贝,回调函数等,因此这种锁机制的使用范围比较窄,适用于读多写少的情况,如网络路由表的查询更新、设备状态表更新等,在业务开发中使用不是很多。

  • 可重入锁和不可重入锁
  • 递归锁recursive mutex 可重入锁(reentrant mutex)
  • 非递归锁non-recursive mutex 不可重入锁(non-reentrant mutex)

Windows下的Mutex和Critical Section是可递归的。Linux下的pthread_mutex_t锁默认是非递归的。在Linux中可以显式设置PTHREAD_MUTEX_RECURSIVE属性,将pthread_mutex_t设为递归锁避免这种场景。 同一个线程可以多次获取同一个递归锁,不会产生死锁。而如果一个线程多次获取同一个非递归锁,则会产生死锁。

如下代码对于非递归锁的死锁示例:

MutexLock mutex;      
void testa()  
{  
    mutex.lock();  
    do_sth();
    mutex.unlock();  
}     
void testb()  
{  
  mutex.lock();   
  testa();  
  mutex.unlock();   
}

代码中testb使用了mutex并且调用testa,但是testa中也调用了相同的mutext,这种场景下如果mutex是非递归的就会出现死锁。

  • 条件变量condition variables

条件变量是用来等待线程而不是上锁的,通常和互斥锁一起使用。互斥锁的一个明显的特点就是某些业务场景中无法借助系统来唤醒,仍然需要业务代码使用while来判断,这样效率本质上比较低。而条件变量通过允许线程阻塞和等待另一个线程发送信号来弥补互斥锁的不足,所以互斥锁和条件变量通常一起使用,来让条件变量异步唤醒阻塞的线程。

条件变量和互斥锁的典型使用就是生产者和消费者模型,这个模型非常经典,也在面试中经常被问到,示例代码:

#include <stdio.h>
#include <pthread.h>
#define MAX 5

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t notfull = PTHREAD_COND_INITIALIZER;  //是否队满
pthread_cond_t notempty = PTHREAD_COND_INITIALIZER; //是否队空
int top = 0;
int bottom = 0;

void* produce(void* arg)
{
    int i;
    for ( i = 0; i < MAX*2; i++)
    {
        pthread_mutex_lock(&mutex);
        while ((top+1)%MAX == bottom)
        {
            printf("full! producer is waiting\n");
            //等待队不满
            pthread_cond_wait(notfull, &mutex);
        }
        top = (top+1) % MAX;
        //发出队非空的消息
        pthread_cond_signal(notempty);
        pthread_mutex_unlock(&mutex);
    }
    return (void*)1;
}
void* consume(void* arg)
{
    int i;
    for ( i = 0; i < MAX*2; i++)
    {
        pthread_mutex_lock(&mutex);
        while ( top%MAX == bottom)
        {
            printf("empty! consumer is waiting\n");
            //等待队不空
            pthread_cond_wait(notempty, &mutex);
        }
        bottom = (bottom+1) % MAX;
        //发出队不满的消息
        pthread_cond_signal(notfull);
        pthread_mutex_unlock(&mutex);
    }
    return (void*)2;
}
int main(int argc, char *argv[])
{
    pthread_t thid1;
    pthread_t thid2;
    pthread_t thid3;
    pthread_t thid4;

    int ret1;
    int ret2;
    int ret3;
    int ret4;

    pthread_create(&thid1, NULL, produce, NULL);
    pthread_create(&thid2, NULL, consume, NULL);
    pthread_create(&thid3, NULL, produce, NULL);
    pthread_create(&thid4, NULL, consume, NULL);

    pthread_join(thid1, (void**)&ret1);
    pthread_join(thid2, (void**)&ret2);
    pthread_join(thid3, (void**)&ret3);
    pthread_join(thid4, (void**)&ret4);
    return 0;
}

其中pthread_cond_wait的使用是个需要注意的地方:pthread_cond_wait()是先将互斥锁解开,并陷入阻塞,直到pthread_signal()发出信号后pthread_cond_wait()再加上锁,然后退出。

Linux内核RCU并发控制技术详解 RCU(Read-Copy Update)是一种在多处理器环境中使用的同步机制,特别适用于读操作远多于写操作的场景。RCU的主要设计目标是在保证数据一致性的同时,尽量减少对系统性能的影响。RCU的关键特性包括:读操作无锁:读取数据时不需要获取锁或其他同步原语,从而显著降低读取延迟。写操作需要拷贝:修改数据前,需要复制数据结构并对其进行修改,这样保证了同时存在的旧读者仍能读取到一致的数据。延迟删除机制:写操作不直接修改原数据,而是删除旧数据并在未来某个时间点进行垃圾回收。 阅读详情

相关推荐

(实战)[re:Invent 2018]-001:赛道分析-(致敬1024)

打印log

Rainbow 2778

初识linux之线程池与一些常见的锁

本文介绍了线程池的概念和简单模拟实现以及一些比较常用的锁,如自旋锁、读写锁的概念

Masquerena114514的博客 786

告别交越失真:手把手教你设计高性能互补输出级电路(LTspice实测)

本文深入剖析音频放大器中常见的交越失真问题,并提供基于LTspice仿真的实战解决方案。通过对比二极管偏置、Vbe倍增器和集成运放驱动三种经典方案,手把手指导设计高性能互补输出级电路,涵盖原理分析、仿真验证及实物调试要点,助力消除音频失真,提升音质。

weixin_29190573的博客 551

Linux 互斥锁 递归锁 自旋锁 读写锁

锁是什么,我们为什么要用到锁?回到问题的本质,我们在什么场景下会用到锁,是在针对于公共资源,也就是临界资源的使用。对于多线程编程,当两个或多个线程同时访问或对一个临界资源操作的时候,为了防止出现数据不一致的问题,我们在使用之前要进行加锁,使用完后进行解锁。 ...

Sakuya__的博客 4288

如何降低 Claude Code 封号风险:实用指南

本文针对 Claude Code 用户频繁遭遇账号封禁的问题,提供了一套经过实战验证的解决方案。通过分析封号的根本原因——共享VPN导致的IP异常标记,文章详细介绍了如何通过"独立服务器+中转服务+环境配置"三步走策略,显著降低封号风险。

goodparty的专栏 2118

[QMT和ptrade量化交易小白入门]-125、菜场大妈加上四大现金行业指数风控,最大回撤从22%减少了一半

群里面有人建议,在菜场大妈策略的基础上,加入申万的行业指数(银行,钢铁,有色,煤炭)测算其宽度,当这四大行业其中有一个高就会清仓(用来控制回撤)。这背后的逻辑是,在存量资金博弈的市场环境中,资金总量相对固定,当银行、钢铁、有色、煤炭等大市值周期行业因基本面改善(如涨价、政策催化)或避险需求而走强时,会吸引大量资金涌入,导致其他板块(尤其是小市值股票)面临流动性虹吸。经过回测,对比上一个版本发现确实是可以降低一半的回撤,最大回撤从22.3%降到了11.4%,而且有很多时间是空仓等待。

木头大左的博客 239

Linux学习——锁

Liinux锁的学习。

qq_41934502的博客 1727

FPGA 学习笔记:Vivado 配置IO引脚约束

FPGA 学习笔记:Vivado 配置IO引脚约束

坚韧与专注 2万+

Linux 中各种锁原理概述

Linux 中各种锁原理概述

云满笔记 833

2026年全国青少年信息素养大赛算法应用主题赛(C++赛项复赛模拟卷1:文末附答案)

2026年全国青少年信息素养大赛算法应用主题赛(C++赛项复赛模拟卷1:文末附答案)

王老师青少年编程 1610

路由三大分类:直连路由、静态路由、动态路由

直连路由、静态路由和动态路由各有其独特的特点和适用场景。直连路由适用于简单的直接连接网络,静态路由提供了精确的路径控制,而动态路由则在大型复杂网络中展现出强大的适应性和自动化能力。了解和掌握这三种路由类型的配置和管理,对于网络管理员来说,是确保网络高效、安全运行的重要技能。

网络技术联盟站 4698

kali锁屏后无法唤醒_Linux中的各种锁及其基本原理

文章源链:Linux中的各种锁及其基本原理​mp.weixin.qq.com公众号:后端技术指南针0.概述通过本文将了解到如下内容:Linux系统的并行性特征互斥和同步机制Linux中常用锁的基本特性互斥锁和条件变量1.Linux的并行性特征Linux作为典型的多用户、多任务、抢占式内核调度的操作系统,为了提高并行处理能力,无论在内核层面还是在用户层面都需要特殊的机制来确保任务的正确性和系统的稳定...

weixin_32382335的博客 858

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

Linux 中,flock 对文件加锁

在Linux中,flock是一个用于对文件加锁的实用程序,它可以帮助协调多个进程对同一个文件的访问,避免出现数据不一致或冲突等问题。以下是对flock。

言之。 1万+

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

[Linux]互斥锁(什么是锁,为什么需要锁,怎么使用锁(接口),什么是死锁,怎么避免死,锁演示代码)

什么是锁?锁是一种同步机制,用于控制多个线程对共享资源的访问。通过锁,可以确保一次只有一个线程能够访问特定的代码段或数据,从而防止数据竞争和不一致。锁的主要目的是确保数据的一致性和线程安全性。为什么需要锁?需要锁的主要原因在于确保多线程或多用户环境中共享资源访问的原子性和数据一致性。在多线程应用中,若多个线程同时访问并修改同一资源,可能导致数据冲突、不一致甚至损坏。

2202_75305885的博客 2467

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

CPU底层锁指令

锁指令在单核显然是原子的,在多核情况下会通过锁总线的方式保证原子性。

howard1209a的博客 568

linux 程序死锁进程状态,linux死锁检测工具lockdep

在Linux系统里,假设有两处代码(比如不同线程的两个函数F1和F2)都要获取两个锁(分别为L1和L2),如果F1持有L1后再去获取L2,而此时恰好由F2持有L2且它也正在尝试获取L1,那么此时就是处于死锁的状态,这是一个最简单的死锁例子,也即所谓的AB-BA死锁。死锁导致的最终结果无需多说,关于如何避免死锁在教科书上也有提到,最简单直观的做法就是按顺序上锁,以破坏死锁的环形等待条件。但对于拥有成...

weixin_39972996的博客 427

linux同步锁总结

Normal 0 7.8 pt 0 2 false false false MicrosoftInternetExplorer4 /* Style De

lyqstudy的技术分享 506

Linux内核锁的应用

一、锁的基本知识 1、锁是通过原子操作实现的,它本身不会导致竞态。 2、为什么要使用锁? 答:因为进程同步的存在,所以要使用锁。 用户空间: (1)用户程序会被调度器抢占和重新调度,用户程序可以在任何时刻被抢占,调度器会选择一个高优先级的进程到处理器上执行。 (2)SMP的存在使得多个处理器同时执行代码。 内核空间: 中断:中断处理程序可能随时打断当前正在执行的代码。 软中断和tasklet:内核...

静虚待令,有物混成 867

Linux内核中的RCU机制详解

int value;// 初始数据// 读取数据// 更新数据return 0;

jiang_style的博客 4351

Linux数据管理——文件锁定

一、什么是文件锁定 对于锁这个字,大家一定不会陌生,因为我们生活中就存在着大量的锁,它们各个方面发挥着它的作用,现在世界中的锁的功能都可归结为一句话,就是阻止某些人做某些事,例如,门锁就是阻止除了屋主之外的人进入这个房子,你进入不到这个房子,也就不能使用房子里面的东西。 而因为程序经常需要共享数据,而这通常又是通过文件来实现的,试想一个情况,A进程正在对一个文件进行写操作,而另一个程序B需...

haierboos 591

linux内核 同步方法

1.完成变量(相当于条件变量) 如果在内核中一个任务需要发出信息号通知另一任务发生了某个特定事件,利用完成变量(completion variable)是使两个任务得以同步的简单方法。 2.BLK(大内核锁) 一种用于完成Linux内核从最初的SMP向细粒度枷锁机制的过度的全局自旋锁。 3.顺序锁 2.6内核新引进的锁机制。它提供了一种很简单的机制,用于读写共享数据。其实现主要依靠一个序

飞翔de刺猬 1138

详解Linux中的锁

spin_lock_irq()/spin_unlock_irq()会禁止/使能中断,另一套函数是spin_lock_irqsave()/spin_unlock_irqrestore(),spin_lock_irqsave()会先保存当前中断状态(使能还是禁止),再禁止中断;这些函数改名为spin_lock_softirq也许更恰当,请记住:spin_lock_bh会禁止Softirq,而不仅仅是禁止“中断下半部”(timer、tasklet里等都是Softirq,中断下半部只是Softirq的一种)。

m0_73726682的博客 1284
上一篇: CNVD 与 CNNVD 的区别
下一篇: 单/半双/全双工和多路复用技术概要
HideInTime
博客等级 码龄9年 86粉丝 131原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值