spinlock和mutexlock的区别和使用场景


  之前虽然有使用过两种类型的锁,但是对于为什么要用,什么时候用,怎么用,为什么设计spin和mutex两种类型的锁等问题并没有认真去了解,对于工作上的问题,只是简单的照猫画虎,已解决问题为目的(实际上这种没有了解基本原理,就对其进行使用是有安全隐患的)
  最近几天工作没有那么累,在设计一个log收集机制的时候,考虑到了这个锁的问题(对于多进程来说是非常重要的,可能不小心就会踩到雷…比方说有多个进程在读写i2c设备,如果没有枷锁的话,是很有可能出现数据异常的结果的(前同事曾遇到过待机唤醒时,i2c写指令出错))

好了,进入正题:


信号量:
  • 信号量只是一个变量,它是非负的,并且在线程之间共享。该变量用于解决多处理环境中的临界区问题和实现进程同步

    • Binary Semaphore 二进制信号量也被称为互斥锁:
      它只能有两个值-0和1。它的值初始化为1。该方法用于求解具有多个进程的临界区问题
    • Counting Semaphore – 计数信号量:
      用于控制对具有多个实例的资源的访问
  • 通过下面P, V两个操作,访问和更改信号量的变量的值
    在这里插入图片描述

    • P 操作又称等待、休眠或下降操作,v 操作又称信号、唤醒或上升操作。两者都是原子操作,信号量的初始值始终是1
      如下图:临界区在两个操作中间,以实现进程同步
      在这里插入图片描述
  • 现在,让我们来看看它是如何实现互斥锁的。设有两个进程 P1和 P2,并将信号量 s 初始化为1。现在假设P1进入临界区,那么信号量 s 的值就变成0。现在,如果 P2想要进入它的临界区,那么它将等待直到 s > 0,这只能在P1完成它的临界区并调用信号量 s 上的 v 操作时发生。通过这种方式,我们实现了互斥锁。查看下面的图像获得详细信息,这是二进制信号量
    在这里插入图片描述

  • 二进制信号量的实现

struct semaphore {
    enum value(0, 1);
 
    // q contains all Process Control Blocks (PCBs)
    // corresponding to processes got blocked
    // while performing down operation.
    Queue<process> q;
 
} P(semaphore s)
{
    if (s.value == 1) {
        s.value = 0;
    }
    else {
        // add the process to the waiting queue
        q.push(P)
        sleep();
    }
}
V(Semaphore s)
{
    if (s.q is empty) {
        s.value = 1;
    }
    else {
 
        // select a process from waiting queue
        s.value = 1;
        Process p=q.pop();
        wakeup(p);
    }
}
  • counting 信号量的信号量,它的值可以大于1。现在假设有一个实例数为4的资源。现在,我们初始化 s = 4,其余部分与二进制信号量相同。每当进程需要这个资源时,它就调用 p 函数或等待函数,完成后,它就调用 v 函数或信号函数。如果 s 的值变为零,那么一个进程必须等到 s 变为正值。例如,假设有4个进程 P1,P2,P3,P4,它们都调用 s 上的等待操作(用4初始化)。如果另一个进程 P5需要资源,那么它应该等待,直到四个进程之一调用信号函数和信号量值为正

  • 计数信号量的实现:

struct Semaphore {
    int value;
 
    // q contains all Process Control Blocks(PCBs)
    // corresponding to processes got blocked
    // while performing down operation.
    Queue<process> q;
 
} P(Semaphore s)
{
    s.value = s.value - 1;
    if (s.value < 0) {
 
        // add process to queue
        // here p is a process which is currently executing
        q.push(p);
        block();
    }
    else
        return;
}
V(Semaphore s)
{
    s.value = s.value + 1;
    if (s.value <= 0) {
 
        // remove process p from queue
        Process p=q.pop();
        wakeup(p);
    }
    else
        return;
}
互斥量:
  • 从上面的描述可知,互斥量也称为二进制信号量,是一种特殊的信号量(初始值为1的信号量),信号量是互斥量的泛化

  • 从上面互斥量的实现代码中不难理解当一个线程试图锁定一个已经被锁定的互斥对象时,它将进入休眠状态,直到互斥对象被之前持有锁的线程解锁。

  • 假设一个进程试图唤醒另一个不处于睡眠状态的进程。可能会导致死锁,会无限期地阻塞


互斥锁和自旋锁的问题及使用场景:
  • 互斥锁

    • 让线程休眠并再次唤醒它们需要大量的 CPU 指令,因此也需要一些时间。如果现在互斥锁只被锁定很短的时间,那么让线程休眠并再次唤醒它所花费的时间可能远远超过线程实际休眠的时间,甚至可能超过线程在自旋锁上不断轮询所浪费的时间,这种情况更适合用spinlock
    • 在多核/多 cpu 系统中,有大量的锁只能保存很短的时间,因此mutex lock不断让线程进入睡眠状态并再次唤醒它们所浪费的时间可能会明显降低运行时性能。当使用自旋锁时,线程有机会充分利用它们的全运行时量程(总是只在很短的时间内阻塞,但随后立即继续工作) ,从而提高处理吞吐量
  • 自旋锁

    • 在单核/单 CPU 系统上使用自旋锁通常没有意义的,因为只要自旋锁轮询阻塞了唯一可用的 CPU 核心,其他线程就无法运行,而且由于没有其他线程可以运行,锁也无法解锁。自旋锁在单核系统上只会浪费 CPU 时间,没有任何实际好处
    • 当一个线程试图锁定一个自旋锁但没有成功时(阻塞或者已经被其他线程持有),它会不断地重新尝试锁定它,直到最终成功; 因此它不会允许另一个线程取代它的位置(然而,一旦当前线程的 CPU 运行时间超过了量,操作系统将强制切换到另一个线程)。
    • 自旋锁上轮询会不断地浪费 CPU 时间,如果锁被保持更长的时间,这将浪费更多的 CPU 时间,这时线程选择处于休眠状态会更好
    • 自旋锁通常使用原子操作实现而不使用操作系统提供的东西
    • 使用自旋锁的主要动机是,如果上下文切换的开销相当于旋转几百次(或者可能上千次) ,但是通过燃烧几个周期的旋转就可以获得锁,那么总的来说这可能更有效

  目前暂时先到这里吧,虽然整理了两天只整理这么一点(对markdown编辑不熟啊!!!)但是对于两者的区分算是了解了吧,哪里描述不对的地方请大家指出,谢谢!!!

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值