Modern C++ std::mutex底层原理

前言

我时常有这样的疑问:

  1. std::mutex怎么就能保证后面的语句100%安全哪?
  2. CPU reordering就不会把这些语句重排到mutex前面执行?
  3. 而且各个CPU都是有L1、L2缓存的,如果mutex后面要访问的的变量在这些缓存中怎么办?

带着这些疑问我们先看看std::mutex是怎么实现的。

std::mutex::lock流程图

先给个图,再细说。

底层原理

先写个简单的cpp程序:

#include <iostream>       // std::cout
#include <thread>         // std::thread
#include <mutex>          // std::mutex, std::lock_guard, std::adopt_lock

std::mutex mtx;           // mutex for critical section
int total=0;

void print_thread_id(int id) {
        mtx.lock();
        for(int i=0;i<10000;i++) total++;
        mtx.unlock();
}

int main()
{
        print_thread_id(1);
        print_thread_id(2);
        std::cout<<"total="<<total<<std::endl;
        return 0;
}

我没起线程啊,只想看看std::mutex是怎么lock的, 这里调用print_thread_id两次也是故意的,因为第一次很多函数符号都没解决(函数@plt),遇到一个函数都要到ld里走一圈,很烦人。而第二次调用就用不着了。

在第二个print_thread_id上加断点,gdb中layout asm看汇编(也可以直接看glibc源码,实际源码有太多宏更不如汇编看的直接):

一路si(单步instruction执行),来到了关键指令:lock cmpxchg

 $r8正好执行我们定义的全局变量mtx, 从上个截图可以看到mtx结构中第一个member是int类型的__lock(看到名字也能盲猜这是mutex::lock的关键),而edi=1。

cmpxchg指令语法如下(请把cmos_lock看成mtx.__lock,  %edx为1):

 cmpxchg会原子的完成下面的IF+赋值,不会让别的线程看到中间状态。

 没被锁的情况下,destination(mtx.__lock)为0,accumulator($EAX)为0(pthread_mutex_lock+91 xor %eax,%eax使得eax的值为0),故ZF标志位被置为1,同时把source(1,pthread_mutex_lock+86 mov 1,%edi)存入mtx.__lock(表示锁上了)。

被别人锁的情况,请看下面的调试:

至于mtx.__lock会不会被各个CPU缓存,或者此指令后面的指令会不会被CPU重排,查了一些资料,也没弄100%明白,姑且先认为是吧。不然这么底层的东西早就造成大量问题了。

附上一些讨论:

concurrency - Is x86 CMPXCHG atomic, if so why does it need LOCK? - Stack Overflowicon-default.png?t=N7T8https://stackoverflow.com/questions/27837731/is-x86-cmpxchg-atomic-if-so-why-does-it-need-lock

multithreading - Do locked instructions provide a barrier between weakly-ordered accesses? - Stack Overflowicon-default.png?t=N7T8https://stackoverflow.com/questions/50280857/do-locked-instructions-provide-a-barrier-between-weakly-ordered-accesses
https://heather.cs.ucdavis.edu/matloff/public_html/50/PLN/lock.pdficon-default.png?t=N7T8https://heather.cs.ucdavis.edu/matloff/public_html/50/PLN/lock.pdf

 如果此处发现mtx.__lock已经是1,也就是别人锁上了,则转系统调用202号(sys_futex)挂起当前线程。

 

  • 25
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
std::atomic是C++标准库中的一个类模板,用于实现原子操作。原子操作可以保证在多线程环境中对共享数据的访问不会发生竞争条件。 为什么已经有互斥量了,还要引入std::atomic呢?这是因为互斥量保护的数据范围比较大,我们期望更小范围的保护。并且当共享数据为一个变量时,原子操作std::atomic的效率更高。 举例来说,假设我们有一个全局的结果数据total,我们想要在多个线程中对它进行无锁访问。如果不使用std::atomic,我们可以使用互斥量进行保护,但是这样的话,所有线程在访问total时都需要获取互斥锁,造成了性能的损失。 而如果我们使用std::atomic<long> total来定义total,我们可以直接通过total进行无锁访问,而不需要使用互斥量。原子操作保证了对total的读写操作是线程安全的,并且性能更好。 所以,当我们需要对共享数据进行无锁访问时,可以考虑使用std::atomic来代替互斥量,以提高程序的性能。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [C++并发编程 | 原子操作std::atomic](https://blog.csdn.net/weixin_44479862/article/details/128059243)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

深山老宅

鸡蛋不错的话,要不要激励下母鸡

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值