一:背景

1. 讲故事

其实这个问题是前段时间有位朋友咨询我的,由于问题说的比较泛,不便作答,但想想梳理一下还是能回答一些的,这篇就来聊一聊下面这几个锁。

  1. Interlocked

  2. AutoResetEvent / ManualResetEvent

  3. Semaphore

用户态层面我就不想说了,网上一搜一大把,我们只聊一聊内核态。

二:锁玩法介绍

1. Interlocked

从各种教科书上就可以知道,这个锁非常轻量级,也是各种高手善用的一把锁,为了方便说明,先上一段代码。


  1. internal class Program
  2. {
  3. static void Main(string[] args)
  4. {
  5. int location = 1;
  6. Interlocked.Increment(ref location);
  7. Console.WriteLine(location);
  8. Debugger.Break();
  9. Interlocked.Increment(ref location);
  10. Console.WriteLine(location);
  11. Console.ReadLine();
  12. }
  13. }

这里我们在第二处 Interlocked.Increment(ref location); 下一个断点,目的是因为此时的 Increment 函数是 JIT 编译后的方法,接下来我们在 WinDbg 中单步调试,会看到如下汇编指令。


  1. 0:000> bp 00007ff8`f6d4298e
  2. 0:000> g
  3. Breakpoint 0 hit
  4. ConsoleApp2!ConsoleApp2.Program.Main+0x4e:
  5. 00007ff8`f6d4298e e84550ffff call 00007ff8`f6d379d8
  6. 0:000> t
  7. 00007ff8`f6d379d8 e9439a7e5a jmp System_Private_CoreLib!System.Int32 System.Threading.Interlocked::Increment(System.Int32&)$##6002C3E (00007ff9`51521420)
  8. 0:000> t
  9. System_Private_CoreLib!System.Threading.Interlocked.Increment:
  10. 00007ff9`51521420 b801000000 mov eax,1
  11. 0:000> t
  12. System_Private_CoreLib!System.Threading.Interlocked.Increment+0x5:
  13. 00007ff9`51521425 f00fc101 lock xadd dword ptr [rcx],eax ds:00000000`001ceb68=00000002

看到上面的 lock xadd 了吗? 原来 Interlocked 类是借助了 CPU 提供的 锁机制 来解决线程同步的, 很显然这种级别的锁相比其他方式的锁性能伤害最小。

2. AutoResetEvent,ManualResetEvent

大家都知道这种锁的名字叫 事件锁, 其实在 Windows 上使用场景特别广,就连监视锁(Monitor) 底层也是用的这种事件锁, 不得不感叹其威力无穷! 而且代码注释中也说了,也就两种状态: 有信号无信号 , 言外之意就是在内核中用了一个 bool 变量来表示,为了能看到这个 bool 值,我们上一个案例。


  1. internal class Program
  2. {
  3. static ManualResetEvent mre = new ManualResetEvent(true);
  4. static void Main(string[] args)
  5. {
  6. Console.WriteLine("handle=" + mre.Handle.ToString("x"));
  7. for (int i = 0; i < 100; i++)
  8. {
  9. mre.Reset();
  10. Console.WriteLine($"{i}:当前为阻塞模式,请观察");
  11. Console.ReadLine();
  12. mre.Set();
  13. Console.WriteLine($"{i}:当前为畅通模式,请观察");
  14. Console.ReadLine();
  15. }
  16. Console.ReadLine();
  17. }
  18. }

为了找到 handle=23c 所对应的内核地址,可以借助 Process Explorer 工具,截图如下:

接下来启动 WinDbg 双机调试,看下内核态上 ffffe00155522220 内存位置的内容。


  1. 0: kd> dp 0xFFFFE00155522220 L1
  2. ffffe001`55522220 00000000`00060000

在控制台上将 ManualResetEvent 设为有信号模式,再次观察这块内存。


  1. 1: kd> dp 0xFFFFE00155522220 L1
  2. ffffe001`55522220 00000001`00060000

大家可以仔细试试看,会发现 ffffe00155522220+0x4 的位置一直都是 0,1 之间的切换,可以推测此时是一个 bool 类型。

有些朋友很好奇,能不能观察看到它的调用栈呢?肯定是可以的,我们使用 ba 下一个硬件断点,观察下它的用户态和内核态栈。


  1. 1: kd> ba w4 0xFFFFE00155522220+0x4
  2. 1: kd> g
  3. Breakpoint 0 hit
  4. nt!KeResetEvent+0x32:
  5. fffff802`f8c3e752 f081237fffffff lock and dword ptr [rbx],0FFFFFF7Fh
  6. 0: kd> k
  7. # Child-SP RetAddr Call Site
  8. 00 ffffd000`ac0cea90 fffff802`f910ebd0 nt!KeResetEvent+0x32
  9. 01 ffffd000`ac0ceac0 fffff802`f8d59b63 nt!NtClearEvent+0x50
  10. 02 ffffd000`ac0ceb00 00007fff`d8963c0a nt!KiSystemServiceCopyEnd+0x13
  11. 03 000000c9`10ece4d8 00007fff`d5e0057a ntdll!NtClearEvent+0xa
  12. 04 000000c9`10ece4e0 00007fff`b88fba05 KERNELBASE!ResetEvent+0xa
  13. 05 000000c9`10ece510 00000000`00000000 System_Private_CoreLib!System.Boolean Interop+Kernel32::ResetEvent(Microsoft.Win32.SafeHandles.SafeWaitHandle)$##60000B0+0x65
  14. ...

从代码中可以看到,命中的是 KeResetEvent 函数,也就是我们用户态代码的 mre.Reset(); 函数,如果大家感兴趣,可以挖一下它的汇编代码,很清楚的看到这个方法中有一些 lock 语句,所以性能上会所有下降哈。

3. Semaphore

要说 Event 事件锁维护的是 bool 变量,那 Semaphore 就属于 int 变量了,为了方便说明继续上一个例子,观察方式和 Event 基本一致。


  1. internal class Program
  2. {
  3. static Semaphore semaphore = new Semaphore(10, 20);
  4. static void Main(string[] args)
  5. {
  6. Console.WriteLine("handle=" + semaphore.Handle.ToString("x"));
  7. for (int i = 0; i < 100; i++)
  8. {
  9. semaphore.WaitOne();
  10. Console.WriteLine($"{i}:已减少 1,请观察");
  11. Console.ReadLine();
  12. }
  13. Console.ReadLine();
  14. }
  15. }

接下来用 WinDbg 进入到本机内核态观察 handle=270 所对应的 内核地址 0xFFFFB58FEA1B1190

从图中可以非常清楚的看到这里的数字在不断的减小,其实想也能想到,少不了一些 CPU 级 lock 锁在里面。

C# 中的那些锁,在内核态都是怎么保证同步的?的更多相关文章

  1. v80.01 鸿蒙内核源码分析(内核态锁篇) | 如何实现快锁Futex(下) | 百篇博客分析OpenHarmony源码

    百篇博客分析|本篇为:(内核态锁篇) | 如何实现快锁Futex(下) 进程通讯相关篇为: v26.08 鸿蒙内核源码分析(自旋锁) | 当立贞节牌坊的好同志 v27.05 鸿蒙内核源码分析(互斥锁) ...

  2. linux 用户态和内核态以及进程上下文、中断上下文 内核空间用户空间理解

    1.特权级         Intel x86架构的cpu一共有0-4四个特权级,0级最高,3级最低,ARM架构也有不同的特权级,硬件上在执行每条指令时都会对指令所具有的特权级做相应的检查.硬件已经提 ...

  3. java中常用的锁机制

    基础知识 基础知识之一:锁的类型 锁就那么几个,只是根据特性,分为不同的类型 锁的概念 在计算机科学中,锁(lock)或互斥(mutex)是一种同步机制,用于在有许多执行线程的环境中强制对资源的访问限 ...

  4. [windows驱动]内核态驱动架构

    1.windows驱动简介: 1.1 windows组件简介: 1.2 windows驱动类型: windows驱动分为两种基本类型: 用户态驱动在用户态下执行.它们一般提供一套win32应用程序和内 ...

  5. 操作系统基本概念(内核态与用户态、操作系统结构)-by sixleaves

    内核态与用户态(为什么存在这种机制.程序应处于哪个状态.如何判断当前所处状态.哪些功能需要内核态.如何实现这种机制) 1.首先我们应该思考清楚为什么会有内核态和用户态?(为什么存在这种机制) 因为计算 ...

  6. Linux内核态用户态相关知识 & 相互通信

    http://www.cnblogs.com/bakari/p/5520860.html 内核从本质上看是一种软件——控制计算机的硬件资源,并提供上层应用程序运行的环境. 系统调用是操作系统的最小功能 ...

  7. (转)linux用户态和内核态理解

    原文:https://blog.csdn.net/buptapple/article/details/21454167 Linux探秘之用户态与内核态-----------https://www.cn ...

  8. cpu与寄存器,内核态与用户态及如何切换

    cpu:相当于计算机的大脑负责运算和发送命令: 寄存器:寄存器是cpu当中的一个有限存储部件,cpu从内存调用数据时,寄存器会将从内存调用的数据进行更新在寄存器中以一个字或变量进行存储. 寄存器总共分 ...

  9. linux用户态和内核态理解

    1.特权级         Intel x86架构的cpu一共有0-4四个特权级,0级最高,3级最低,硬件上在执行每条指令时都会对指令所具有的特权级做相应的检查.硬件已经提供了一套特权级使用的相关机制 ...

随机推荐

  1. namespace_std 杂题选讲

    CF1458C Latin Square 2021 EC Final C. Random Shuffle [THUPC2021] 混乱邪恶 [JOISC2022] 制作团子 3 2022 集训队互测 ...

  2. NuGetTools:批量上传、删除和显示NuGet包

    快照 前言 NuGet是.NET开发必不可少的包管理工具,在日常更新版本过程中,可能需要批量发布 NuGet 包,也不可避免需要发布一些测试的包,后期想将这些测试或者过期的包删除掉.nuget.org ...

  3. Graphics2D类

    Graphics2D类 Java语言在Graphics类提供绘制各种基本的几何图形的基础上,扩展Graphics类提供一个Graphics2D类,它拥用更强大的二维图形处理能力,提供.坐标转换.颜色管 ...

  4. [原创]移远RM500U-CN模组驱动移植

    1. 简介 中国广电正式放号了,为了支持广电700MHz的5G基站,需要换用新的5G模组.移远通信的RM500U模组正好可以满足我们的使用要求; 我们选用该模组的原因:双卡单待 支持SIM卡热插拔 支 ...

  5. Kafka Topic Partition Offset 这一长串都是啥?

    摘要:Offset 偏移量,是针对于单个partition存在的概念. 本文分享自华为云社区<Kafka Topic Partition Offset 这一长串都是啥?>,作者: gent ...

  6. day02_基本语法

    基本语法 学习目标: 1. 数据类型 2. 变量 3. 编码介绍 4. 标识符和关键字 5. 字符串类型 6. 数据类型转化 7. 进制转换 8. 运算符 一.数据类型 什么是数据类型? 在开发软件的 ...

  7. .NET 使用自带 DI 批量注入服务(Service)和 后台服务(BackgroundService)

    今天教大家如何在asp .net core 和 .net 控制台程序中 批量注入服务和 BackgroundService 后台服务 在默认的 .net 项目中如果我们注入一个服务或者后台服务,常规的 ...

  8. HBase学习(四) 二级索引 rowkey设计

    HBase学习(四) 一.HBase的读写流程 画出架构 1.1 HBase读流程 Hbase读取数据的流程:1)是由客户端发起读取数据的请求,首先会与zookeeper建立连接2)从zookeepe ...

  9. 【做题笔记】CF Edu Round 132

    1. 前言 本人第一次把 Div2. D 切了,开心. C 不会,寄. 后来在场外想到一种奇怪做法 AC 了. 2. 正文(A-D) CF 比赛链接 A. Three Doors 签到题.循环查找手中 ...

  10. 【ASP.NET Core】选项模式的相关接口

    在 .NET 中,配置与选项模式其实有联系的(这些功能现在不仅限于 ASP.NET Core,而是作为平台扩展来提供,在其他.NET 项目中都能用).配置一般从多个来源(上一篇水文中的例子,记得否?) ...