深入探究C/C++中互斥量(锁)的实现原理

 更新时间:2024年06月02日 09:12:02   作者:螺蛳粉只吃炸蛋的走风  
​ 互斥量是一种同步原语,用于保护多个线程同时访问共享数据,互斥量提供独占的、非递归的所有权语义,本文将和大家一起深入探究C/C++中互斥量(锁)的实现原理,感兴趣的小伙伴跟着小编一起来看看吧

互斥量的概念

互斥量(mutex)是一种同步原语,用于保护多个线程同时访问共享数据。互斥量提供独占的、非递归的所有权语义:一个线程从成功调用locktry_lock开始,到调用unlock结束,都拥有互斥量。

何为原子性操作

程序的原子性指:整个程序中的所有操作,要么全部完成,要么全部不完成,不可能停滞在中间某个环节。

原子性在一个操作是不可中断的,要么全部执行成功要么全部执行失败,有着 “同生共死” 的感觉。在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程所干扰

如果要保证原子性,必须符合以下两条规则:

运算结果并不依赖于变量的当前值,或者能够确保只有一个线程修改变量的值。

变量不需要与其他的状态变量共同参与不变约束。

原理探究

首先给出一段加锁场景的部分代码:

void route(ThreadData *td)
{
    // 加锁
    while (true)
    {
        pthread_mutex_lock(&td->_mutex);    // 加锁
        if (td->_tickets > 0)
        {
            // 模拟一次抢票的逻辑
            usleep(1000);
            printf("%s running, get tickets: %d\n", td->_name.c_str(), td->_tickets);
            td->_tickets--;
            pthread_mutex_unlock(&td->_mutex); // 解锁
            td->_total++;
        }
        else
        {
            pthread_mutex_unlock(&td->_mutex); // 解锁
            break;
        }
    }
}

上面这段代码模拟了抢票逻辑,将多线程并行抢票通过锁的加入变为串行执行,有效避免了恶意数据竞争(data race)

我们不妨假定有两个线程同时执行到 加锁指令 位置:

在这里插入图片描述

(上图左侧部分为加锁和解锁对应的汇编语言代码,其中每一行简单汇编指令的执行都是原子的)

不妨设定 thread-1 先进入 lock 逻辑 (thread-2先进入同理,不影响推断):

(这里的先进入 lock 逻辑,实际上指的是先执行左侧汇编语言中 xchgb &al, mutex 语句)

这就意味着 thread-1先执行交换语句,将系统指定初始的 mutex 值 (存储在内存中) 与寄存器初始值 0 进行交换,从而寄存器中值变为1。

在这里插入图片描述

由于汇编语言简单语句的单行执行是原子的,此时thread-1 已经执行完 xchgb &al, mutex 语句,所以不排除 thread-2 紧接着也执行 xchgb &al, mutex 语句的可能。(线程被切换的时机是随时的)

这时我们需要注意:

  • CPU寄存器的硬件只有一套,但是寄存器内的数据,属于线程的硬件上下文 !
  • 数据在内存中存储时,所有线程都能访问,属于共享资源,但是当数据从内存移动到寄存器时,就属于一个线程私有了 !

当执行线程从 thread-1 变为 thread-2 时,隶属于 thread-1 的寄存器硬件上下文被取走,thread-1::%al 寄存器值为1,CPU内%al寄存器值恢复为空。

所以,当 thread-2 执行 xchgb &al, mutex 语句时,访问到寄存器内存储的内容为自身线程所属寄存器的初始值(thread-2 先前执行了 moveb $0, %al ,所以初始值为0),由于内存中 mutex 初始值1已经被 thread-1 交换取走,此时内存中 mutex 的值为0,进行交换后 %al寄存器 中的值依然为0。

经过汇编的下层判断语句 if(%al寄存器内容 > 0) 不符合条件,故 thread-2 没有成功获得锁,需要执行 goto lock 语句重新申请锁的资源。

在这里插入图片描述

综上我们可以看到,所有线程在争锁的时候,只有一个 1 !!!

至此,我们发现 thread-2 想要继续执行,就必须等待 thread-1 释放锁,所以程序的执行流程就由 thread-1 执行到释放锁结束后,将内存中 mutex 变量置为1,thread-2 才终止等待,获得 thread-1 释放的锁后,执行自身的代码逻辑。

引入锁的用途就是为了解决并发访问出现的问题,其问题的本质是多个执行流同时执行访问全局数据的代码造成的。使用锁保护全局共享资源的本质是通过保护临界区完成的

以上就是深入探究C/C++中互斥量(锁)的实现原理的详细内容,更多关于C/C++互斥量(锁)实现的资料请关注脚本之家其它相关文章!

相关文章

  • C++中priority_queue模拟实现

    C++中priority_queue模拟实现

    本文主要介绍了C++中priority_queue模拟实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2026-04-04
  • Qt的qDebug使用小结

    Qt的qDebug使用小结

    使用qDebug()函数它可以把调试信息直接输出到控制台上,本文就来介绍一下qDebug的具体使用,具有一定的参考价值,感兴趣的可以了解一下
    2023-06-06
  • C++顺序表实现图书管理系统

    C++顺序表实现图书管理系统

    这篇文章主要为大家详细介绍了C++顺序表实现图书管理系统,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-10-10
  • 如何在C语言中判断socket是否已经断开

    如何在C语言中判断socket是否已经断开

    如果不主动关闭socket的话,系统不会自动关闭的,除非当前进程挂掉了,操作系统把占用的socket回收了才会关闭。小编今天跟大家简单介绍下如何在C语言中判断socket是否已经断开
    2019-05-05
  • C++简单实现消息队列的示例代码

    C++简单实现消息队列的示例代码

    消息队列是一种应用间的通讯方式,消息发送后可以立即放回,由消息系统来确保消息的可靠传递,本文主要介绍了C++简单实现消息队列的示例代码,感兴趣的可以了解一下
    2025-11-11
  • C++中#pragma once与#ifndef对比分析

    C++中#pragma once与#ifndef对比分析

    当我们编写C++代码时,经常需要使用头文件来引入一些常用的函数、类或者变量,如果一个头文件被重复包含,就会导致编译错误或者运行时错,为了避免发生,我们需要使用预处理指令来防止头文件被重复包含,常用的预处理指令有#pragma once和#ifndef,需要的朋友可以参考下
    2023-05-05
  • OpenCV实现帧差法检测运动目标

    OpenCV实现帧差法检测运动目标

    这篇文章主要为大家详细介绍了OpenCV实现帧差法检测运动目标,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-03-03
  • C++ system()函数的常用用法(全网最新)

    C++ system()函数的常用用法(全网最新)

    system()用于从C /C++程序调用操作系统命令,这里给大家讲解下C++ system()函数的常用用法,感兴趣的朋友跟随小编一起看看吧
    2023-01-01
  • Qt专栏之模态与非模态对话框的实现

    Qt专栏之模态与非模态对话框的实现

    这篇文章主要介绍了Qt专栏之模态与非模态对话框的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-04-04
  • VC++在TXT文件指定位置追加内容的方法

    VC++在TXT文件指定位置追加内容的方法

    这篇文章主要介绍了VC++在TXT文件指定位置追加内容的方法,功能较为实用,需要的朋友可以参考下
    2014-08-08

最新评论