C# 中的那些锁,在内核态都是怎么保证同步的?
一:背景
1. 讲故事
其实这个问题是前段时间有位朋友咨询我的,由于问题说的比较泛,不便作答,但想想梳理一下还是能回答一些的,这篇就来聊一聊下面这几个锁。
Interlocked
AutoResetEvent / ManualResetEvent
Semaphore
用户态层面我就不想说了,网上一搜一大把,我们只聊一聊内核态。
二:锁玩法介绍
1. Interlocked
从各种教科书上就可以知道,这个锁非常轻量级,也是各种高手善用的一把锁,为了方便说明,先上一段代码。
internal class Program
{
static void Main(string[] args)
{
int location = 1;
Interlocked.Increment(ref location);
Console.WriteLine(location);
Debugger.Break();
Interlocked.Increment(ref location);
Console.WriteLine(location);
Console.ReadLine();
}
}
这里我们在第二处 Interlocked.Increment(ref location);
下一个断点,目的是因为此时的 Increment
函数是 JIT 编译后的方法,接下来我们在 WinDbg 中单步调试,会看到如下汇编指令。
0:000> bp 00007ff8`f6d4298e
0:000> g
Breakpoint 0 hit
ConsoleApp2!ConsoleApp2.Program.Main+0x4e:
00007ff8`f6d4298e e84550ffff call 00007ff8`f6d379d8
0:000> t
00007ff8`f6d379d8 e9439a7e5a jmp System_Private_CoreLib!System.Int32 System.Threading.Interlocked::Increment(System.Int32&)$##6002C3E (00007ff9`51521420)
0:000> t
System_Private_CoreLib!System.Threading.Interlocked.Increment:
00007ff9`51521420 b801000000 mov eax,1
0:000> t
System_Private_CoreLib!System.Threading.Interlocked.Increment+0x5:
00007ff9`51521425 f00fc101 lock xadd dword ptr [rcx],eax ds:00000000`001ceb68=00000002
看到上面的 lock xadd
了吗? 原来 Interlocked
类是借助了 CPU 提供的 锁机制 来解决线程同步的, 很显然这种级别的锁相比其他方式的锁性能伤害最小。
2. AutoResetEvent,ManualResetEvent
大家都知道这种锁的名字叫 事件锁
, 其实在 Windows 上使用场景特别广,就连监视锁(Monitor) 底层也是用的这种事件锁, 不得不感叹其威力无穷! 而且代码注释中也说了,也就两种状态: 有信号
和 无信号
, 言外之意就是在内核中用了一个 bool
变量来表示,为了能看到这个 bool 值,我们上一个案例。
internal class Program
{
static ManualResetEvent mre = new ManualResetEvent(true);
static void Main(string[] args)
{
Console.WriteLine("handle=" + mre.Handle.ToString("x"));
for (int i = 0; i < 100; i++)
{
mre.Reset();
Console.WriteLine($"{i}:当前为阻塞模式,请观察");
Console.ReadLine();
mre.Set();
Console.WriteLine($"{i}:当前为畅通模式,请观察");
Console.ReadLine();
}
Console.ReadLine();
}
}
为了找到 handle=23c
所对应的内核地址,可以借助 Process Explorer
工具,截图如下:
接下来启动 WinDbg 双机调试,看下内核态上 ffffe00155522220
内存位置的内容。
0: kd> dp 0xFFFFE00155522220 L1
ffffe001`55522220 00000000`00060000
在控制台上将 ManualResetEvent
设为有信号模式,再次观察这块内存。
1: kd> dp 0xFFFFE00155522220 L1
ffffe001`55522220 00000001`00060000
大家可以仔细试试看,会发现 ffffe00155522220+0x4
的位置一直都是 0,1 之间的切换,可以推测此时是一个 bool 类型。
有些朋友很好奇,能不能观察看到它的调用栈呢?肯定是可以的,我们使用 ba
下一个硬件断点,观察下它的用户态和内核态栈。
1: kd> ba w4 0xFFFFE00155522220+0x4
1: kd> g
Breakpoint 0 hit
nt!KeResetEvent+0x32:
fffff802`f8c3e752 f081237fffffff lock and dword ptr [rbx],0FFFFFF7Fh
0: kd> k
# Child-SP RetAddr Call Site
00 ffffd000`ac0cea90 fffff802`f910ebd0 nt!KeResetEvent+0x32
01 ffffd000`ac0ceac0 fffff802`f8d59b63 nt!NtClearEvent+0x50
02 ffffd000`ac0ceb00 00007fff`d8963c0a nt!KiSystemServiceCopyEnd+0x13
03 000000c9`10ece4d8 00007fff`d5e0057a ntdll!NtClearEvent+0xa
04 000000c9`10ece4e0 00007fff`b88fba05 KERNELBASE!ResetEvent+0xa
05 000000c9`10ece510 00000000`00000000 System_Private_CoreLib!System.Boolean Interop+Kernel32::ResetEvent(Microsoft.Win32.SafeHandles.SafeWaitHandle)$##60000B0+0x65
...
从代码中可以看到,命中的是 KeResetEvent
函数,也就是我们用户态代码的 mre.Reset();
函数,如果大家感兴趣,可以挖一下它的汇编代码,很清楚的看到这个方法中有一些 lock 语句,所以性能上会所有下降哈。
3. Semaphore
要说 Event 事件锁维护的是 bool 变量,那 Semaphore 就属于 int 变量了,为了方便说明继续上一个例子,观察方式和 Event 基本一致。
internal class Program
{
static Semaphore semaphore = new Semaphore(10, 20);
static void Main(string[] args)
{
Console.WriteLine("handle=" + semaphore.Handle.ToString("x"));
for (int i = 0; i < 100; i++)
{
semaphore.WaitOne();
Console.WriteLine($"{i}:已减少 1,请观察");
Console.ReadLine();
}
Console.ReadLine();
}
}
接下来用 WinDbg 进入到本机内核态观察 handle=270
所对应的 内核地址 0xFFFFB58FEA1B1190
。
从图中可以非常清楚的看到这里的数字在不断的减小,其实想也能想到,少不了一些 CPU 级 lock 锁在里面。
C# 中的那些锁,在内核态都是怎么保证同步的?的更多相关文章
- v80.01 鸿蒙内核源码分析(内核态锁篇) | 如何实现快锁Futex(下) | 百篇博客分析OpenHarmony源码
百篇博客分析|本篇为:(内核态锁篇) | 如何实现快锁Futex(下) 进程通讯相关篇为: v26.08 鸿蒙内核源码分析(自旋锁) | 当立贞节牌坊的好同志 v27.05 鸿蒙内核源码分析(互斥锁) ...
- linux 用户态和内核态以及进程上下文、中断上下文 内核空间用户空间理解
1.特权级 Intel x86架构的cpu一共有0-4四个特权级,0级最高,3级最低,ARM架构也有不同的特权级,硬件上在执行每条指令时都会对指令所具有的特权级做相应的检查.硬件已经提 ...
- java中常用的锁机制
基础知识 基础知识之一:锁的类型 锁就那么几个,只是根据特性,分为不同的类型 锁的概念 在计算机科学中,锁(lock)或互斥(mutex)是一种同步机制,用于在有许多执行线程的环境中强制对资源的访问限 ...
- [windows驱动]内核态驱动架构
1.windows驱动简介: 1.1 windows组件简介: 1.2 windows驱动类型: windows驱动分为两种基本类型: 用户态驱动在用户态下执行.它们一般提供一套win32应用程序和内 ...
- 操作系统基本概念(内核态与用户态、操作系统结构)-by sixleaves
内核态与用户态(为什么存在这种机制.程序应处于哪个状态.如何判断当前所处状态.哪些功能需要内核态.如何实现这种机制) 1.首先我们应该思考清楚为什么会有内核态和用户态?(为什么存在这种机制) 因为计算 ...
- Linux内核态用户态相关知识 & 相互通信
http://www.cnblogs.com/bakari/p/5520860.html 内核从本质上看是一种软件——控制计算机的硬件资源,并提供上层应用程序运行的环境. 系统调用是操作系统的最小功能 ...
- (转)linux用户态和内核态理解
原文:https://blog.csdn.net/buptapple/article/details/21454167 Linux探秘之用户态与内核态-----------https://www.cn ...
- cpu与寄存器,内核态与用户态及如何切换
cpu:相当于计算机的大脑负责运算和发送命令: 寄存器:寄存器是cpu当中的一个有限存储部件,cpu从内存调用数据时,寄存器会将从内存调用的数据进行更新在寄存器中以一个字或变量进行存储. 寄存器总共分 ...
- linux用户态和内核态理解
1.特权级 Intel x86架构的cpu一共有0-4四个特权级,0级最高,3级最低,硬件上在执行每条指令时都会对指令所具有的特权级做相应的检查.硬件已经提供了一套特权级使用的相关机制 ...
随机推荐
- CF141E Clearing Up 题解
思路分析 自认为是一道很好的思维题. 直接看上去的想法是: 跑一个生成树,每一次加的边颜色交替进行,直到拉出生成树. 仔细想想,发现可能无法保证最后是一棵树而不是森林,也是说输出都是 \(-1\) . ...
- 数据结构-二叉树(Binary Tree)
1.二叉树(Binary Tree) 是n(n>=0)个结点的有限集合,该集合或者为空集(空二叉树),或者由一个根节点和两棵互不相交的,分别称为根节点的左子树和右子树的二叉树组成. 2.特数二 ...
- Pytorch分布式训练
用单机单卡训练模型的时代已经过去,单机多卡已经成为主流配置.如何最大化发挥多卡的作用呢?本文介绍Pytorch中的DistributedDataParallel方法. 1. DataParallel ...
- httpdns是个什么技术,有什么用
dns解析现状问题1:暴利的dns劫持 要说为啥会出现httpdns(先不用管意思,后面解释),那么,首先要说一下,现在的dns解析,是不是有啥问题? dns能有啥问题呢,就是输入一个域名xxx.co ...
- Rust 从入门到精通01-简介
1.rust 从哪里来 Rust语言在2006年作为 Mozilla 员工 Graydon Hoare 的私人项目出现,而 Mozilla 于 2009 年开始赞助这个项目.第一个有版本号的 Rust ...
- 第三讲 Linux测试
3.1 Linux操作系统定义 Ø我们为什么要学习这个linux系统呢? 那是因为我们很多的服务都放在这个linux系统,那为什么很多服务都要放到这个linux系统?这是因为linux系统好,它系统稳 ...
- 技术分享|闪回在MySQL中的实现和改进
GreatSQL社区原创内容未经授权不得随意使用,转载请联系小编并注明来源. 3306π 福州站,以下内容是由万里数据库,研发工程师唐洁分享的MySQL闪回方案完整PPT. Enjoy GreatSQ ...
- 在Ubuntu下编译安装GreatSQL
在Ubuntu下编译安装GreatSQL 本次介绍如何利用Docker构建Ubuntu环境,并将GreatSQL源码编译成二进制文件. 1.准备工作 先创建本次Docker的workdir为 /dat ...
- Docker 链接sqlserver时出现en-us is an invalid culture错误解决方案
在部署服务到docker的时候出现全球化错误 System . Global ization . Cul tureNotFoundException: Only the invariant cultu ...
- Luogu2073 送花 (平衡树)
打感叹号处为傻逼处 #include <iostream> #include <cstdio> #include <cstring> #include <al ...