经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » ASP.net » 查看文章
Net 高级调试之十四:线程同步的基础知识和常见的同步原语
来源:cnblogs  作者:可均可可  时间:2023/12/21 9:05:40  对本文有异议
一、介绍
    今天是《Net 高级调试》的第十四篇文章,这篇文章我们主要介绍和线程相关的内容,当然不是教你如何去写多线程,更不会介绍多线程的使用方法和API,今天,我们主要讲一下锁,一说到多线程,就会有并发的问题,也可以说是线程安全的问题,锁是没有办法避开的一个话题。我们今天不讲锁的使用方法,主要是关注锁的底层实现原理,是如何实现的,让我们做到知其一,也要知其二,这些是 Net 框架的底层,了解更深,对于我们调试更有利。当然了,第一次看视频或者看书,是很迷糊的,不知道如何操作,还是那句老话,一遍不行,那就再来一遍,还不行,那就再来一遍,俗话说的好,书读千遍,其意自现。
     如果在没有说明的情况下,所有代码的测试环境都是 Net Framewok 4.8,但是,有时候为了查看源码,可能需要使用 Net Core 的项目,我会在项目章节里进行说明。好了,废话不多说,开始我们今天的调试工作。

       调试环境我需要进行说明,以防大家不清楚,具体情况我已经罗列出来。
          操作系统:Windows Professional 10
          调试工具:Windbg Preview(可以去Microsoft Store 去下载)
          开发工具:Visual Studio 2022
          Net 版本:Net Framework 4.8
          CoreCLR源码:源码下载

二、基础知识
    1、线程同步原语
        1.1、C# Thread 的表示。
            我们在C# 程序中书写一个 Thread 线程类,其实,在背后会做很多事情,比如在 CLR 层会有一个对应的线程类生成,同时操作系统层也会有一个数据结构与之对应,所以说,我们简简单单声明一个 Thread 类,会有三个数据结构来承载。
            
            a)、C# 层的 Thread。
                C# 中的 Thread 类,其实是对 CLR 层 Thread 线程类的封装,在 C# Thread 类的定义中,会有一个 private IntPtr DONT_USE_InternalThread 实例字段,该字段就是引用的 CLR 层的线程指针引用。

            b)、CLR 层的 Thread
                Net Core 是开源的,所以是可以看到 CLR 线程 Thread 的定义。类名是:Thread.cpp,Net 5、6、7、8都可以看。

            c)、OS 层的 KThread。
                操作系统层的线程对象是通过 _KThread 来表示的。

    2、事件原语
        2.1、AutoResetEvent 和 ManulResetEvent(内核锁)
            事件同步的本质实在内核态维护了一个 bool 值,通过 bool 值来实现线程间的同步,具体的使用方法网上很多,我这里就不过多的赘述了,这里我们看看是如何通过 bool 值的变化实现线程间的同步的。

        2.2、Semaphore(内核锁)
            AutoResetEvent、ManulResetEvent 维护的是 bool 类型的值,信号量本质上就是维护了一个 int 值,这就是两者的区别,我们可以使用 Windbg 来查看一下 waitHandle 的值,可以发现 Semaphore 的 Count 的值在不断的变化。

        2.3、Monitor(混合锁-内核锁)
           监视器是由 C# 中的 AwareLock 实现的,底层是基于 AutoResetEvent 机制,可以参见 coreclr 源码。因为 Monitor 是基于对象头的同步块索引来实现的,我们可以查看对象头的数据结构就可以明白了。
 
        2.4、ThinLock(用户态锁)
            【瘦锁】也是 CLR 基于【对象头】实现的一种轻量级的自旋锁,没有和内核态交互,所以性能非常高,这种实现的方式就是将【持有锁】线程的 Id 存在在对象头中,如果【对象头】中存不下就会转换成 Monitor 锁。

三、源码调试
    废话不多说,这一节是具体的调试过程,又可以说是眼见为实的过程,在开始之前,我还是要啰嗦两句,这一节分为两个部分,第一部分是测试的源码部分,没有代码,当然就谈不上测试了,调试必须有载体。第二部分就是根据具体的代码来证实我们学到的知识,是具体的眼见为实。
    1、调试源码
        1.1、Example_14_1_1
  1. 1 using System;
  2. 2 using System.Threading;
  3. 3
  4. 4 namespace Example_14_1_1
  5. 5 {
  6. 6 internal class Program
  7. 7 {
  8. 8 static void Main(string[] args)
  9. 9 {
  10. 10 var thread = new Thread(() =>
  11. 11 {
  12. 12 Console.WriteLine($"tid={Environment.CurrentManagedThreadId}");
  13. 13 Console.ReadLine();
  14. 14 });
  15. 15
  16. 16 thread.Start();
  17. 17
  18. 18 Console.ReadLine();
  19. 19 }
  20. 20 }
  21. 21 }
View Code

        1.2、Example_14_1_2
  1. 1 using System;
  2. 2 using System.Diagnostics;
  3. 3 using System.Threading;
  4. 4
  5. 5 namespace Example_14_1_2
  6. 6 {
  7. 7 internal class Program
  8. 8 {
  9. 9 public static ManualResetEvent mre = new ManualResetEvent(false);
  10. 10
  11. 11 static void Main(string[] args)
  12. 12 {
  13. 13 Console.WriteLine($"mre 默认为 false,即等待状态,请查看!");
  14. 14 Debugger.Break();
  15. 15
  16. 16 mre.Set();
  17. 17 Console.WriteLine($"mre 默认为 true,即放行状态,请查看!");
  18. 18 Debugger.Break();
  19. 19
  20. 20 mre.Reset();
  21. 21 Console.WriteLine($"mre Reset后为 false,即等待状态,请查看!");
  22. 22 Debugger.Break();
  23. 23 }
  24. 24 }
  25. 25 }
View Code

        1.3、Example_14_1_3
  1. 1 using System;
  2. 2 using System.Diagnostics;
  3. 3 using System.Threading;
  4. 4
  5. 5 namespace Example_14_1_3
  6. 6 {
  7. 7 internal class Program
  8. 8 {
  9. 9 public static Semaphore sem = new Semaphore(1, 10);
  10. 10 static void Main(string[] args)
  11. 11 {
  12. 12 for (int i = 0; i < int.MaxValue; i++)
  13. 13 {
  14. 14 sem.Release();
  15. 15 Console.WriteLine("查看当前的 sem 值。");
  16. 16 Debugger.Break();
  17. 17 }
  18. 18 }
  19. 19 }
  20. 20 }
View Code

        1.4、Example_14_1_4(Net 7.0)
  1. 1 using System.Diagnostics;
  2. 2
  3. 3 namespace Example_14_1_4_Core
  4. 4 {
  5. 5 internal class Program
  6. 6 {
  7. 7 public static Person person = new Person();
  8. 8
  9. 9 static void Main(string[] args)
  10. 10 {
  11. 11 Task.Run(() =>
  12. 12 {
  13. 13 lock (person)
  14. 14 {
  15. 15 Console.WriteLine($"{Environment.CurrentManagedThreadId} 已进入 Person 锁中111111");
  16. 16 Debugger.Break();
  17. 17 }
  18. 18 });
  19. 19 Task.Run(() =>
  20. 20 {
  21. 21 lock (person)
  22. 22 {
  23. 23 Console.WriteLine($"{Environment.CurrentManagedThreadId} 已进入 Person 锁中222222");
  24. 24 Debugger.Break();
  25. 25 }
  26. 26 });
  27. 27 Console.ReadLine();
  28. 28 }
  29. 29 }
  30. 30
  31. 31 public class Person
  32. 32 {
  33. 33 }
  34. 34 }
View Code

        1.5、Example_14_1_5
  1. 1 using System;
  2. 2 using System.Diagnostics;
  3. 3 using System.Threading.Tasks;
  4. 4
  5. 5 namespace Example_14_1_5
  6. 6 {
  7. 7 internal class Program
  8. 8 {
  9. 9 public static Person person = new Person();
  10. 10
  11. 11 static void Main(string[] args)
  12. 12 {
  13. 13 Task.Run(() =>
  14. 14 {
  15. 15 lock (person)
  16. 16 {
  17. 17 Console.WriteLine($"{Environment.CurrentManagedThreadId} 已进入 Person 锁中");
  18. 18 Debugger.Break();
  19. 19 }
  20. 20 });
  21. 21 Console.ReadLine();
  22. 22 }
  23. 23 }
  24. 24
  25. 25 public class Person
  26. 26 {
  27. 27 }
  28. 28 }
View Code

    2、眼见为实
        
        2.1、我们查看 C# Thread 线程所对应的 OS 层的数据结构表示。
            调试源码:Example_14_1_1
            这个项目调试的方法是不一样的,在这里,我们直接打开Debug 目录下的 EXE 应用程序,直接双击运行程序,程序启动成功,在控制台中输出:tid=3,这个值大家可能不一样。程序运行成功,就产生了一个线程对象。我们想要查看内核态线程的id,需要在借助一个【ProcessExplorer】工具,这个工具有32位和64位两个版本,根据自己系统特特性选择合适的版本,我选择的是64位版本的。效果如图:
            

            程序运行起来如下:
            

            接着,我们在过【通过名称过滤(Filter by name)】中输入我们项目的名称:Example_14_1_1,来进程查找。效果如图:
            
            我们在找到的进程上双击破,打开新窗口,如图:
            
            我们找到了我们项目进程的主键线程编号,然后就可以使用 Windbg 查看内核态的线程表示了。我们主线程的编号是:1204,这个是十进制的,要注意。
            然后,我们打开 Windbg,点击【File】-->【Attach to kernel(附加内核态)】,在右侧选择【local】,就是本机的内核态,点击【ok】按钮,进入调试界面。然后,我们使用【process】命令查找一下我们的项目。

  1. 1 lkd> !process 0 2 Example_14_1_1.exe
  2. 2 PROCESS ffff9004b47eb080
  3. 3 SessionId: 1 Cid: 3a0c Peb: 00322000 ParentCid: 24bc
  4. 4 DirBase: 36353c002 ObjectTable: ffffc6096ce7b180 HandleCount: 194.
  5. 5 Image: Example_14_1_1.exe
  6. 6
  7. 7 THREAD ffff9004b64d2080 Cid 3a0c.04b4 Teb: 0000000000324000 Win32Thread: ffff9004b7232db0 WAIT: (Executive) KernelMode Alertable
  8. 8 ffff9004b7310e68 NotificationEvent
  9. 9
  10. 10 THREAD ffff9004b42e70c0 Cid 3a0c.0fb8 Teb: 000000000032d000 Win32Thread: 0000000000000000 WAIT: (UserRequest) UserMode Non-Alertable
  11. 11 ffff9004b7352ae0 SynchronizationEvent
  12. 12 ffff9004b7352760 SynchronizationEvent
  13. 13 ffff9004b73524e0 SynchronizationEvent
  14. 14
  15. 15 THREAD ffff9004b6b4f100 Cid 3a0c.3ab8 Teb: 0000000000330000 Win32Thread: 0000000000000000 WAIT: (UserRequest) UserMode Non-Alertable
  16. 16 ffff9004a72b2b20 NotificationEvent
  17. 17 ffff9004b7352660 SynchronizationEvent
  18. 18 ffff9004b4d35a90 SynchronizationEvent
  19. 19
  20. 20 THREAD ffff9004b63ea080 Cid 3a0c.318c Teb: 0000000000333000 Win32Thread: 0000000000000000 WAIT: (UserRequest) UserMode Alertable
  21. 21 ffff9004b7353560 SynchronizationEvent

            他会把这个进程中的所有线程找出来。然后,我们点击【break】按钮,我们通过【ProcessExploler】看到我们项目的主线程是:1204,这个值是十进制的,我们看看十六进制是多少。

  1. 1 lkd> ?0n1204
  2. 2 Evaluate expression: 1204 = 00000000`000004b4

            然后,我们使用04b4查找一下,效果如图:
            

            ffff9004b64d2080 这个值就是线程的内核态的数据结构,我们可以继续使用【dt】命令查看一下详情。

  1. 1 lkd> dt nt!_KThread ffff9004b64d2080
  2. 2 +0x000 Header : _DISPATCHER_HEADER
  3. 3 +0x018 SListFaultAddress : (null)
  4. 4 +0x020 QuantumTarget : 0x9c1aedd
  5. 5 +0x028 InitialStack : 0xfffff48b`1c777c50 Void
  6. 6 +0x030 StackLimit : 0xfffff48b`1c771000 Void
  7. 7 +0x038 StackBase : 0xfffff48b`1c778000 Void
  8. 8 +0x040 ThreadLock : 0
  9. 9 +0x048 CycleTime : 0x766fe16
  10. 10 +0x050 CurrentRunTime : 0
  11. 11 +0x054 ExpectedRunTime : 0x589722
  12. 12 +0x058 KernelStack : 0xfffff48b`1c777570 Void
  13. 13 +0x060 StateSaveArea : 0xfffff48b`1c777c80 _XSAVE_FORMAT
  14. 14 +0x068 SchedulingGroup : (null)
  15. 15 +0x070 WaitRegister : _KWAIT_STATUS_REGISTER
  16. 16 +0x071 Running : 0 ''
  17. 17 +0x072 Alerted : [2] ""
  18. 18 +0x074 AutoBoostActive : 0y1
  19. 19 +0x074 ReadyTransition : 0y0
  20. 20 +0x074 WaitNext : 0y0
  21. 21 +0x074 SystemAffinityActive : 0y0
  22. 22 +0x074 Alertable : 0y1
  23. 23 +0x074 UserStackWalkActive : 0y0
  24. 24 +0x074 ApcInterruptRequest : 0y0
  25. 25 +0x074 QuantumEndMigrate : 0y0
  26. 26 +0x074 UmsDirectedSwitchEnable : 0y0
  27. 27 +0x074 TimerActive : 0y0
  28. 28 +0x074 SystemThread : 0y0
  29. 29 +0x074 ProcessDetachActive : 0y0
  30. 30 +0x074 CalloutActive : 0y0
  31. 31 +0x074 ScbReadyQueue : 0y0
  32. 32 +0x074 ApcQueueable : 0y1
  33. 33 +0x074 ReservedStackInUse : 0y0
  34. 34 +0x074 UmsPerformingSyscall : 0y0
  35. 35 +0x074 TimerSuspended : 0y0
  36. 36 +0x074 SuspendedWaitMode : 0y0
  37. 37 +0x074 SuspendSchedulerApcWait : 0y0
  38. 38 +0x074 CetUserShadowStack : 0y0
  39. 39 +0x074 BypassProcessFreeze : 0y0
  40. 40 +0x074 Reserved : 0y0000000000 (0)
  41. 41 +0x074 MiscFlags : 0n16401
  42. 42 +0x078 ThreadFlagsSpare : 0y00
  43. 43 +0x078 AutoAlignment : 0y1
  44. 44 +0x078 DisableBoost : 0y0
  45. 45 +0x078 AlertedByThreadId : 0y0
  46. 46 +0x078 QuantumDonation : 0y1
  47. 47 +0x078 EnableStackSwap : 0y1
  48. 48 +0x078 GuiThread : 0y1
  49. 49 +0x078 DisableQuantum : 0y0
  50. 50 +0x078 ChargeOnlySchedulingGroup : 0y0
  51. 51 +0x078 DeferPreemption : 0y0
  52. 52 +0x078 QueueDeferPreemption : 0y0
  53. 53 +0x078 ForceDeferSchedule : 0y0
  54. 54 +0x078 SharedReadyQueueAffinity : 0y1
  55. 55 +0x078 FreezeCount : 0y0
  56. 56 +0x078 TerminationApcRequest : 0y0
  57. 57 +0x078 AutoBoostEntriesExhausted : 0y1
  58. 58 +0x078 KernelStackResident : 0y1
  59. 59 +0x078 TerminateRequestReason : 0y00
  60. 60 +0x078 ProcessStackCountDecremented : 0y0
  61. 61 +0x078 RestrictedGuiThread : 0y0
  62. 62 +0x078 VpBackingThread : 0y0
  63. 63 +0x078 ThreadFlagsSpare2 : 0y0
  64. 64 +0x078 EtwStackTraceApcInserted : 0y00000000 (0)
  65. 65 +0x078 ThreadFlags : 0n205028
  66. 66 +0x07c Tag : 0 ''
  67. 67 +0x07d SystemHeteroCpuPolicy : 0 ''
  68. 68 +0x07e UserHeteroCpuPolicy : 0y0001000 (0x8)
  69. 69 +0x07e ExplicitSystemHeteroCpuPolicy : 0y0
  70. 70 +0x07f RunningNonRetpolineCode : 0y0
  71. 71 +0x07f SpecCtrlSpare : 0y0000000 (0)
  72. 72 +0x07f SpecCtrl : 0 ''
  73. 73 +0x080 SystemCallNumber : 0x1a0006
  74. 74 +0x084 ReadyTime : 1
  75. 75 +0x088 FirstArgument : 0x00000000`00000094 Void
  76. 76 +0x090 TrapFrame : 0xfffff48b`1c777ac0 _KTRAP_FRAME
  77. 77 +0x098 ApcState : _KAPC_STATE
  78. 78 +0x098 ApcStateFill : [43] "???"
  79. 79 +0x0c3 Priority : 9 ''
  80. 80 +0x0c4 UserIdealProcessor : 2
  81. 81 +0x0c8 WaitStatus : 0n256
  82. 82 +0x0d0 WaitBlockList : 0xffff9004`b64d21c0 _KWAIT_BLOCK
  83. 83 +0x0d8 WaitListEntry : _LIST_ENTRY [ 0x00000000`00000000 - 0xffff9004`b1e95158 ]
  84. 84 +0x0d8 SwapListEntry : _SINGLE_LIST_ENTRY
  85. 85 +0x0e8 Queue : (null)
  86. 86 +0x0f0 Teb : 0x00000000`00324000 Void
  87. 87 +0x0f8 RelativeTimerBias : 0
  88. 88 +0x100 Timer : _KTIMER
  89. 89 +0x140 WaitBlock : [4] _KWAIT_BLOCK
  90. 90 +0x140 WaitBlockFill4 : [20] "p???"
  91. 91 +0x154 ContextSwitches : 0xee
  92. 92 +0x140 WaitBlockFill5 : [68] "p???"
  93. 93 +0x184 State : 0x5 ''
  94. 94 +0x185 Spare13 : 0 ''
  95. 95 +0x186 WaitIrql : 0 ''
  96. 96 +0x187 WaitMode : 0 ''
  97. 97 +0x140 WaitBlockFill6 : [116] "p???"
  98. 98 +0x1b4 WaitTime : 0x780fc
  99. 99 +0x140 WaitBlockFill7 : [164] "p???"
  100. 100 +0x1e4 KernelApcDisable : 0n-1
  101. 101 +0x1e6 SpecialApcDisable : 0n0
  102. 102 +0x1e4 CombinedApcDisable : 0xffff
  103. 103 +0x140 WaitBlockFill8 : [40] "p???"
  104. 104 +0x168 ThreadCounters : (null)
  105. 105 +0x140 WaitBlockFill9 : [88] "p???"
  106. 106 +0x198 XStateSave : (null)
  107. 107 +0x140 WaitBlockFill10 : [136] "p???"
  108. 108 +0x1c8 Win32Thread : 0xffff9004`b7232db0 Void
  109. 109 +0x140 WaitBlockFill11 : [176] "p???"
  110. 110 +0x1f0 Ucb : (null)
  111. 111 +0x1f8 Uch : (null)
  112. 112 +0x200 ThreadFlags2 : 0n0
  113. 113 +0x200 BamQosLevel : 0y00000000 (0)
  114. 114 +0x200 ThreadFlags2Reserved : 0y000000000000000000000000 (0)
  115. 115 +0x204 Spare21 : 0
  116. 116 +0x208 QueueListEntry : _LIST_ENTRY [ 0x00000000`00000000 - 0x00000000`00000000 ]
  117. 117 +0x218 NextProcessor : 0
  118. 118 +0x218 NextProcessorNumber : 0y0000000000000000000000000000000 (0)
  119. 119 +0x218 SharedReadyQueue : 0y0
  120. 120 +0x21c QueuePriority : 0n0
  121. 121 +0x220 Process : 0xffff9004`b47eb080 _KPROCESS
  122. 122 +0x228 UserAffinity : _GROUP_AFFINITY
  123. 123 +0x228 UserAffinityFill : [10] "???"
  124. 124 +0x232 PreviousMode : 1 ''
  125. 125 +0x233 BasePriority : 8 ''
  126. 126 +0x234 PriorityDecrement : 0 ''
  127. 127 +0x234 ForegroundBoost : 0y0000
  128. 128 +0x234 UnusualBoost : 0y0000
  129. 129 +0x235 Preempted : 0 ''
  130. 130 +0x236 AdjustReason : 0 ''
  131. 131 +0x237 AdjustIncrement : 0 ''
  132. 132 +0x238 AffinityVersion : 0x28
  133. 133 +0x240 Affinity : _GROUP_AFFINITY
  134. 134 +0x240 AffinityFill : [10] "???"
  135. 135 +0x24a ApcStateIndex : 0 ''
  136. 136 +0x24b WaitBlockCount : 0x1 ''
  137. 137 +0x24c IdealProcessor : 2
  138. 138 +0x250 NpxState : 5
  139. 139 +0x258 SavedApcState : _KAPC_STATE
  140. 140 +0x258 SavedApcStateFill : [43] "???"
  141. 141 +0x283 WaitReason : 0 ''
  142. 142 +0x284 SuspendCount : 0 ''
  143. 143 +0x285 Saturation : 0 ''
  144. 144 +0x286 SListFaultCount : 0
  145. 145 +0x288 SchedulerApc : _KAPC
  146. 146 +0x288 SchedulerApcFill0 : [1] "??????"
  147. 147 +0x289 ResourceIndex : 0x1 ''
  148. 148 +0x288 SchedulerApcFill1 : [3] "???"
  149. 149 +0x28b QuantumReset : 0x6 ''
  150. 150 +0x288 SchedulerApcFill2 : [4] "???"
  151. 151 +0x28c KernelTime : 3
  152. 152 +0x288 SchedulerApcFill3 : [64] "???"
  153. 153 +0x2c8 WaitPrcb : (null)
  154. 154 +0x288 SchedulerApcFill4 : [72] "???"
  155. 155 +0x2d0 LegoData : (null)
  156. 156 +0x288 SchedulerApcFill5 : [83] "???"
  157. 157 +0x2db CallbackNestingLevel : 0 ''
  158. 158 +0x2dc UserTime : 0
  159. 159 +0x2e0 SuspendEvent : _KEVENT
  160. 160 +0x2f8 ThreadListEntry : _LIST_ENTRY [ 0xffff9004`b42e73b8 - 0xffff9004`b47eb0b0 ]
  161. 161 +0x308 MutantListHead : _LIST_ENTRY [ 0xffff9004`b64d2388 - 0xffff9004`b64d2388 ]
  162. 162 +0x318 AbEntrySummary : 0x3e '>'
  163. 163 +0x319 AbWaitEntryCount : 0 ''
  164. 164 +0x31a AbAllocationRegionCount : 0 ''
  165. 165 +0x31b SystemPriority : 0 ''
  166. 166 +0x31c SecureThreadCookie : 0
  167. 167 +0x320 LockEntries : 0xffff9004`b64d26d0 _KLOCK_ENTRY
  168. 168 +0x328 PropagateBoostsEntry : _SINGLE_LIST_ENTRY
  169. 169 +0x330 IoSelfBoostsEntry : _SINGLE_LIST_ENTRY
  170. 170 +0x338 PriorityFloorCounts : [16] ""
  171. 171 +0x348 PriorityFloorCountsReserved : [16] ""
  172. 172 +0x358 PriorityFloorSummary : 0
  173. 173 +0x35c AbCompletedIoBoostCount : 0n0
  174. 174 +0x360 AbCompletedIoQoSBoostCount : 0n0
  175. 175 +0x364 KeReferenceCount : 0n0
  176. 176 +0x366 AbOrphanedEntrySummary : 0 ''
  177. 177 +0x367 AbOwnedEntryCount : 0x1 ''
  178. 178 +0x368 ForegroundLossTime : 0
  179. 179 +0x370 GlobalForegroundListEntry : _LIST_ENTRY [ 0x00000000`00000001 - 0x00000000`00000000 ]
  180. 180 +0x370 ForegroundDpcStackListEntry : _SINGLE_LIST_ENTRY
  181. 181 +0x378 InGlobalForegroundList : 0
  182. 182 +0x380 ReadOperationCount : 0n12
  183. 183 +0x388 WriteOperationCount : 0n0
  184. 184 +0x390 OtherOperationCount : 0n293
  185. 185 +0x398 ReadTransferCount : 0n27743
  186. 186 +0x3a0 WriteTransferCount : 0n0
  187. 187 +0x3a8 OtherTransferCount : 0n9406
  188. 188 +0x3b0 QueuedScb : (null)
  189. 189 +0x3b8 ThreadTimerDelay : 0
  190. 190 +0x3bc ThreadFlags3 : 0n0
  191. 191 +0x3bc ThreadFlags3Reserved : 0y00000000 (0)
  192. 192 +0x3bc PpmPolicy : 0y00
  193. 193 +0x3bc ThreadFlags3Reserved2 : 0y0000000000000000000000 (0)
  194. 194 +0x3c0 TracingPrivate : [1] 0
  195. 195 +0x3c8 SchedulerAssist : (null)
  196. 196 +0x3d0 AbWaitObject : (null)
  197. 197 +0x3d8 ReservedPreviousReadyTimeValue : 0
  198. 198 +0x3e0 KernelWaitTime : 0xe
  199. 199 +0x3e8 UserWaitTime : 0
  200. 200 +0x3f0 GlobalUpdateVpThreadPriorityListEntry : _LIST_ENTRY [ 0x00000000`00000001 - 0x00000000`00000000 ]
  201. 201 +0x3f0 UpdateVpThreadPriorityDpcStackListEntry : _SINGLE_LIST_ENTRY
  202. 202 +0x3f8 InGlobalUpdateVpThreadPriorityList : 0
  203. 203 +0x400 SchedulerAssistPriorityFloor : 0n0
  204. 204 +0x404 Spare28 : 0
  205. 205 +0x408 EndPadding : [5] 0
View Code

            大家感兴趣的,可以打开看看,内容还是不少的。
            当然,我们也可以通过 Windbg 直接查看了,我们的项目正在执行中,所以我们可以通过【Attach to process】进入调试界面,然后,通过【!t】或者【!threads】命令,查看线程三者的对应关系。

  1. 1 0:004> !t
  2. 2 ThreadCount: 3
  3. 3 UnstartedThread: 0
  4. 4 BackgroundThread: 1
  5. 5 PendingThread: 0
  6. 6 DeadThread: 0
  7. 7 Hosted Runtime: no
  8. 8 Lock
  9. 9 ID OSID ThreadOBJ State GC Mode GC Alloc Context Domain Count Apt Exception
  10. 10 0 1 4b4 00696b10 2a020 Preemptive 02506254:00000000 006903d0 1 MTA
  11. 11 2 2 3ab8 00698df8 2b220 Preemptive 00000000:00000000 006903d0 0 MTA (Finalizer)
  12. 12 3 3 318c 006ee308 202b020 Preemptive 0250501C:00000000 006903d0 0 MTA
  13. 13 0:004> !threads
  14. 14 ThreadCount: 3
  15. 15 UnstartedThread: 0
  16. 16 BackgroundThread: 1
  17. 17 PendingThread: 0
  18. 18 DeadThread: 0
  19. 19 Hosted Runtime: no
  20. 20 Lock
  21. 21 ID OSID ThreadOBJ State GC Mode GC Alloc Context Domain Count Apt Exception
  22. 22 0 1 4b4 00696b10 2a020 Preemptive 02506254:00000000 006903d0 1 MTA
  23. 23 2 2 3ab8 00698df8 2b220 Preemptive 00000000:00000000 006903d0 0 MTA (Finalizer)
  24. 24 3 3 318c 006ee308 202b020 Preemptive 0250501C:00000000 006903d0 0 MTA

            ID是1就是C#的托管线程编号, OSID的值是4b4就是操作系统层面的线程的数据结构,ThreadOBJ 就是CLR 层面的线程。    


        2.2、我们看看 AutoResetEvent 是如何通过 bool 值变化实现线程间的同步的。
            调试源码:Example_14_1_2
            我们编译项目,打开 Windbg,点击【文件】----》【launch executable】附加程序,打开调试器的界面,程序已经处于中断状态。我们需要使用【g】命令,继续运行程序,在【Debugger.Break()】语句处停止,我们的控制台应用程序输出:mre 默认为 false,即等待状态,请查看!Windbg 处于暂停状态,我们就可以调试了。
            首先,我们去托管堆中查找一下 ManualResetEvent 这个对象,执行【!dumpheap -type ManualResetEvent】命令
  1. 1 1:000> !dumpheap -type ManualResetEvent
  2. 2 Address MT Size
  3. 3 033e24d4 6d53d578 24
  4. 4
  5. 5 Statistics:
  6. 6 MT Count TotalSize Class Name
  7. 7 6d53d578 1 24 System.Threading.ManualResetEvent
  8. 8 Total 1 objects

            红色标注的地址就是我们要找的 ManualResetEvent 的实例。我们继续使用【!do】命令查看详情。

  1. 1 1:000> !do 033e24d4
  2. 2 Name: System.Threading.ManualResetEvent
  3. 3 MethodTable: 6d53d578
  4. 4 EEClass: 6d6114d0
  5. 5 Size: 24(0x18) bytes
  6. 6 File: C:\Windows\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
  7. 7 Fields:
  8. 8 MT Field Offset Type VT Attr Value Name
  9. 9 6d4f2734 40005ba 4 System.Object 0 instance 00000000 __identity
  10. 10 6d4f7b18 4001990 c System.IntPtr 1 instance 2f8 waitHandle
  11. 11 6d4f6688 4001991 8 ...es.SafeWaitHandle 0 instance 033e2504 safeWaitHandle
  12. 12 6d4f878c 4001992 10 System.Boolean 1 instance 0 hasThreadAffinity
  13. 13 6d4f7b18 4001993 ec8 System.IntPtr 1 shared static InvalidHandle
  14. 14 >> Domain:Value 016adb18:ffffffff <<

            红色标注的是一个 handle 对象,我们可以使用【!handle 2f8 f】命令继续查看,必须具有 f 参数。

  1. 1 1:000> !handle 2f8 f
  2. 2 Handle 2f8
  3. 3 Type Event
  4. 4 Attributes 0
  5. 5 GrantedAccess 0x1f0003:
  6. 6 Delete,ReadControl,WriteDac,WriteOwner,Synch
  7. 7 QueryState,ModifyState
  8. 8 HandleCount 2
  9. 9 PointerCount 32769
  10. 10 Name <none>
  11. 11 Object Specific Information
  12. 12 Event Type Manual Reset(事件类型是 ManualResetEvent
  13. 13 Event is Waiting(当前是等待状态)

            说明 false 是等待的状态,然后,我们继续【g】运行一下,等我们的控制台项目输出:mre 默认为 true,即放行状态,请查看!我们继续执行【!handle 2f8 f】命令查看。

  1. 1 1:000> !handle 2f8 f
  2. 2 Handle 2f8
  3. 3 Type Event
  4. 4 Attributes 0
  5. 5 GrantedAccess 0x1f0003:
  6. 6 Delete,ReadControl,WriteDac,WriteOwner,Synch
  7. 7 QueryState,ModifyState
  8. 8 HandleCount 2
  9. 9 PointerCount 65536
  10. 10 Name <none>
  11. 11 Object Specific Information
  12. 12 Event Type Manual Reset
  13. 13 Event is Set

            然后,我们继续【g】运行一下,等我们的控制台项目输出:mre Reset后为 false,即等待状态,请查看!我们继续执行【!handle 2f8 f】命令查看。

  1. 1 1:000> !handle 2f8 f
  2. 2 Handle 2f8
  3. 3 Type Event
  4. 4 Attributes 0
  5. 5 GrantedAccess 0x1f0003:
  6. 6 Delete,ReadControl,WriteDac,WriteOwner,Synch
  7. 7 QueryState,ModifyState
  8. 8 HandleCount 2
  9. 9 PointerCount 65535
  10. 10 Name <none>
  11. 11 Object Specific Information
  12. 12 Event Type Manual Reset
  13. 13 Event is Waiting

 

            我们都知道 AutoResetEvent 和 ManulResetEvent 的功能就是 Windows 底层的功能,说白了就是 C# 只是使用了 Windows 内核提供的事件,C# 不过是对其进行了包装,如果你想要查看内存地址,必须到内核态去看。

        2.3、如何到内核态去查看 AutoResetEvent 和 ManulResetEvent 地址。
            调试源码:Example_14_1_2
            在这里,我们要打开两个 Windbg,第一个 Windbg 我们查看一下用户态。我们编译程序,通过【File】-->【launche executing】附加我们的可执行程序。进入到调试器界面,我们继续【g】,我们的控制台应用程序输出:mre 默认为 false,即等待状态,请查看!调试器处于中断状态,我们就可以调试了。
            我们首先要找到【ManualResetEvent】对象的事件句柄,执行命令【!dumpheap -type ManualResetEvent】命令。
  1. 0:000> !dumpheap -type ManualResetEvent
  2. Address MT Size
  3. 033224d4 6d53d578 24
  4. Statistics:
  5. MT Count TotalSize Class Name
  6. 6d53d578 1 24 System.Threading.ManualResetEvent
  7. Total 1 objects

            红色标注的就是【ManualResetEvent】对象地址,我们可以使用【!dumpobj /d 033224d4】命令查看 ManualResetEvent 实例对象。

  1. 1 0:000> !dumpobj /d 033224d4
  2. 2 Name: System.Threading.ManualResetEvent
  3. 3 MethodTable: 6d53d578
  4. 4 EEClass: 6d6114d0
  5. 5 Size: 24(0x18) bytes
  6. 6 File: C:\Windows\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
  7. 7 Fields:
  8. 8 MT Field Offset Type VT Attr Value Name
  9. 9 6d4f2734 40005ba 4 System.Object 0 instance 00000000 __identity
  10. 10 6d4f7b18 4001990 c System.IntPtr 1 instance 2dc waitHandle(我们要查找的事件句柄)
  11. 11 6d4f6688 4001991 8 ...es.SafeWaitHandle 0 instance 03322504 safeWaitHandle
  12. 12 6d4f878c 4001992 10 System.Boolean 1 instance 0 hasThreadAffinity
  13. 13 6d4f7b18 4001993 ec8 System.IntPtr 1 shared static InvalidHandle
  14. 14 >> Domain:Value 01532430:ffffffff <<

            我们再打开一个 Windbg,查看内核态,点击【File】-->【Attach to Kernel】,右侧选择【local】,点击【ok】进入调试器界面。2dc是一个句柄,就像一个编号,我们还需要借助【Process Explorer】工具,我们打开这个工具,然后在【Filter by name】输入项目名称Example_14_1_2,结果如图:
            

            我们在 0X000002dc行双击,打开新窗口,效果如图:
            

            我们就找到了内核地址了。然后,我们到 Windbg 的内核态中去查看一下这个地址,使用【dp】命令。当前值:0(00000000

  1. 1 lkd> dp 0xFFFF9004B7B916E0 l1
  2. 2 ffff9004`b7b916e0 00000000`00060000

            然后我们【g】一下用户态的 Windbg,控制台输出:mre 默认为 true,即放行状态,请查看!当前值:1(00000001),然后切换到【内核态】的Windbg,继续使用【dp】命令,查看一下。

  1. 1 lkd> dp 0xFFFF9004B7B916E0 l1
  2. 2 ffff9004`b7b916e0 00000001`00060000

            然后,我们再【g】一下【用户态】的Windbg,控制台输出:mre Reset后为 false,即等待状态,请查看!当前值:0(00000000),然后切换到【内核态】的Windbg,继续使用【dp】命令,查看一下。

  1. 1 lkd> dp 0xFFFF9004B7B916E0 l1
  2. 2 ffff9004`b7b916e0 00000000`00060000

            我们就看到了,状态是0和1相互切换的。


        2.4、我们查看 Semaphore Count 的值是如何变化的。
            调试源码:Example_14_1_3
            我们编译项目,打开 Windbg,点击【文件】----》【launch executable】附加程序,打开调试器的界面,程序已经处于中断状态。我们需要使用【g】命令,继续运行程序,在【Debugger.Break()】语句处停止,我们的控制台应用程序输出:查看当前的 sem 值。现在就可以调试程序了。
            我们现在托管堆中查找一下 Semaphore 对象,我们可以使用【!dumpheap -type Semaphore】命令。
  1. 1 0:000> !dumpheap -type Semaphore
  2. 2 Address MT Size
  3. 3 02f924d4 6d59611c 24
  4. 4
  5. 5 Statistics:
  6. 6 MT Count TotalSize Class Name
  7. 7 6d59611c 1 24 System.Threading.Semaphore
  8. 8 Total 1 objects

            红色标注的地址 02f924d4 就是 Semaphore 对象,然后,我们可以使用【!do 02f924d4】或者【!dumpobj /d 02f924d4】查看 Semaphore 对象的详情,两个命令执行的结果都是一样的。

  1. 1 0:000> !do 02f924d4
  2. 2 Name: System.Threading.Semaphore
  3. 3 MethodTable: 6d59611c
  4. 4 EEClass: 6d5ccfa0
  5. 5 Size: 24(0x18) bytes
  6. 6 File: C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System\v4.0_4.0.0.0__b77a5c561934e089\System.dll
  7. 7 Fields:
  8. 8 MT Field Offset Type VT Attr Value Name
  9. 9 6ec52734 40005ba 4 System.Object 0 instance 00000000 __identity
  10. 10 6ec57b18 4001990 c System.IntPtr 1 instance 314 waitHandle
  11. 11 6ec56688 4001991 8 ...es.SafeWaitHandle 0 instance 02f92504 safeWaitHandle
  12. 12 6ec5878c 4001992 10 System.Boolean 1 instance 0 hasThreadAffinity
  13. 13 6ec57b18 4001993 ec8 System.IntPtr 1 shared static InvalidHandle
  14. 14 >> Domain:Value 010dd880:ffffffff <<
  15. 15
  16. 16
  17. 17 0:000> !dumpobj /d 02f924d4
  18. 18 Name: System.Threading.Semaphore
  19. 19 MethodTable: 6d59611c
  20. 20 EEClass: 6d5ccfa0
  21. 21 Size: 24(0x18) bytes
  22. 22 File: C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System\v4.0_4.0.0.0__b77a5c561934e089\System.dll
  23. 23 Fields:
  24. 24 MT Field Offset Type VT Attr Value Name
  25. 25 6ec52734 40005ba 4 System.Object 0 instance 00000000 __identity
  26. 26 6ec57b18 4001990 c System.IntPtr 1 instance 314 waitHandle
  27. 27 6ec56688 4001991 8 ...es.SafeWaitHandle 0 instance 02f92504 safeWaitHandle
  28. 28 6ec5878c 4001992 10 System.Boolean 1 instance 0 hasThreadAffinity
  29. 29 6ec57b18 4001993 ec8 System.IntPtr 1 shared static InvalidHandle
  30. 30 >> Domain:Value 010dd880:ffffffff <<

            Semaphore 其实也是一个 waitHandle,我们有了 handle 地址,就可以使用【!handle】命令了。

  1. 1 Handle 314
  2. 2 Type Semaphore
  3. 3 Attributes 0
  4. 4 GrantedAccess 0x1f0003:
  5. 5 Delete,ReadControl,WriteDac,WriteOwner,Synch
  6. 6 QueryState,ModifyState
  7. 7 HandleCount 2
  8. 8 PointerCount 65536
  9. 9 Name <none>
  10. 10 Object Specific Information
  11. 11 Semaphore Count 2Semaphore sem = new Semaphore(1, 10),我们初始值是1,当前值是2
  12. 12 Semaphore Limit 10

            我们继续【g】,然后再次执行【!handle 314 f】命令,再次查看,Semaphore Count 的值就是3。

  1. 1 0:000> !handle 314 f
  2. 2 Handle 314
  3. 3 Type Semaphore
  4. 4 Attributes 0
  5. 5 GrantedAccess 0x1f0003:
  6. 6 Delete,ReadControl,WriteDac,WriteOwner,Synch
  7. 7 QueryState,ModifyState
  8. 8 HandleCount 2
  9. 9 PointerCount 65535
  10. 10 Name <none>
  11. 11 Object Specific Information
  12. 12 Semaphore Count 3(现在值是:3,每运行一次,该值就增加1,调用 Release()函数一次,值就增加一次。)
  13. 13 Semaphore Limit 10(这个值是极限值)

            我们继续【g】,然后再次执行【!handle 314 f】命令,再次查看,Semaphore Count 值肯定就是4了。

  1. 1 0:000> !handle 314 f
  2. 2 Handle 314
  3. 3 Type Semaphore
  4. 4 Attributes 0
  5. 5 GrantedAccess 0x1f0003:
  6. 6 Delete,ReadControl,WriteDac,WriteOwner,Synch
  7. 7 QueryState,ModifyState
  8. 8 HandleCount 2
  9. 9 PointerCount 65534
  10. 10 Name <none>
  11. 11 Object Specific Information
  12. 12 Semaphore Count 4(又增加了一次)
  13. 13 Semaphore Limit 10

 

            这个变化的 Count 值的内存地址在哪里呢?其实它的功能都是有内核态提供的,如果想看 Count 的内存地址,必须找到内核态的地址。这里我们还是需要借用【Process Explorer】工具,我使用的64位版本,自己可以根据自己系统的特点选择。打开工具,过滤我们的项目【Example_14_1_3】。效果如图:

            
            双击【Semaphore】条目打卡属性窗口,就能看到它的内核态的内存地址了。
            

            我们有了内核态的内存地址,就需要再打开一个 Windbg,点击【File】-->【Attach to Kernel】,在右侧窗口选择【local】点击【ok】打开调试器。然后,我们就可以使用【dp】命令查看具体的值了,当前值是:4(00000004)。

  1. 1 lkd> dp 0xFFFFCE09E477EA60 l1
  2. 2 ffffce09`e477ea60 00000004`00080005

            我们切换到第一个 Windbg 窗口,【g】继续运行,然后再切换回来这个 Windbg,再次运行【dp】命令,当前的值应该就是:5(00000005)。

  1. 1 lkd> dp 0xFFFFCE09E477EA60 l1
  2. 2 ffffce09`e477ea60 00000005`00080005

            其实,我们可以在第一个 Windbg 窗口中,使用【!handle 314 f】也可以看到结果值,肯定也是5。

  1. 1 0:000> !handle 314 f
  2. 2 Handle 314
  3. 3 Type Semaphore
  4. 4 Attributes 0
  5. 5 GrantedAccess 0x1f0003:
  6. 6 Delete,ReadControl,WriteDac,WriteOwner,Synch
  7. 7 QueryState,ModifyState
  8. 8 HandleCount 2
  9. 9 PointerCount 65532
  10. 10 Name <none>
  11. 11 Object Specific Information
  12. 12 Semaphore Count 5
  13. 13 Semaphore Limit 10

                Semaphore 是有极限值的,如果超过极限值,CLR 会抛出异常。

  1. 1 0:000> g
  2. 2 ModLoad: 058f0000 059ea000 image058f0000
  3. 3 ModLoad: 059f0000 05aea000 image059f0000
  4. 4 (52c.2990): CLR exception - code e0434352 (first chance)
  5. 5 ModLoad: 66bf0000 66cf5000 C:\Windows\Microsoft.NET\Framework\v4.0.30319\diasymreader.dll
  6. 6 ModLoad: 6cd30000 6d548000 C:\Windows\assembly\NativeImages_v4.0.30319_32\System.Core\System.Core.ni.dll
  7. 7 ModLoad: 73630000 73643000 C:\Windows\SysWOW64\CRYPTSP.dll
  8. 8 ModLoad: 73600000 7362f000 C:\Windows\SysWOW64\rsaenh.dll
  9. 9 ModLoad: 75ce0000 75cf9000 C:\Windows\SysWOW64\bcrypt.dll
  10. 10 ModLoad: 73690000 7369a000 C:\Windows\SysWOW64\CRYPTBASE.dll
  11. 11 (52c.2990): CLR exception - code e0434352 (!!! second chance !!!)
  12. 12 *** WARNING: Unable to verify checksum for C:\Windows\assembly\NativeImages_v4.0.30319_32\\System.ni.dll
  13. 13 eax=00f3ec58 ebx=00000005 ecx=00000005 edx=00000000 esi=00f3ed1c edi=00000001
  14. 14 eip=75969862 esp=00f3ec58 ebp=00f3ecb4 iopl=0 nv up ei pl nz ac po nc
  15. 15 cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000212
  16. 16 KERNELBASE!RaiseException+0x62:
  17. 17 75969862 8b4c2454 mov ecx,dword ptr [esp+54h] ss:002b:00f3ecac=c4689540

            

        2.5、我们使用 Windbg 查看 Monitor 的实现,该项目是 Net 7.0,因为Net Framework 是闭源的,没有办法看到源码。
            调试源码:Example_14_1_4
            我们编译项目,打开 Windbg,点击【文件】----》【launch executable】附加程序,打开调试器的界面,程序已经处于中断状态。我们需要使用【g】命令,继续运行程序,当我们的控制台程序输出:4 已进入 Person 锁中111111(这里不一定是这个,我的输出是这个),这个过程的时间有点长,Windbg执行框处在【busy】状态, 因为正在下载 coreclr.pdb,下载完毕就可以,操作完成,Windbg 有一个 int 3 中断,就可以调试程序了。
            然后,我们使用【!syncblk】命令,查看一下同步块。

  1. 1 0:007> !syncblk
  2. 2 Index SyncBlock   MonitorHeld   Recursion   Owning Thread Info SyncBlock Owner
  3. 3 9 0000026C988CE368   3   1   000002AD2EE66800 18b0 7 0000026c9cc0cb88 Example_14_1_4_Core.Person
  4. 4 -----------------------------
  5. 5 Total 12
  6. 6 CCW 0
  7. 7 RCW 0
  8. 8 ComClassFactory 0
  9. 9 Free 0


            我们说过 Monitor 的底层实现就是 AwareLock,这个标红 0000026C988CE368 地址就是指向  AwareLock。我们使用【dt】命令查看一番。

  1. 1 0:007> dt coreclr!AwareLock 0000026C988CE368
  2. 2 +0x000 m_lockState : AwareLock::LockState(底层的 awarelock
  3. 3 +0x004 m_Recursion : 1(递归次数1
  4. 4 +0x008 m_HoldingThread : 0x000002ad`2ee66800 Thread(持有的线程,和 Owning Thread Info 值一样)
  5. 5 +0x010 m_TransientPrecious : 0n1
  6. 6 +0x014 m_dwSyncIndex : 0x80000009(这个就是同步块索引,是9)
  7. 7 +0x018 m_SemEvent : CLREvent(底层还是使用的 Event 实现同步)
  8. 8 +0x028 m_waiterStarvationStartTimeMs : 0xb6cb0b

            我们继续使用【dx】命令查看 m_SemEvent 是什么。

  1. 1 0:007> dx -r1 (*((coreclr!CLREvent *)0x26c988ce380))
  2. 2 (*((coreclr!CLREvent *)0x26c988ce380)) [Type: CLREvent]
  3. 3 [+0x000] m_handle : 0x2d0 [Type: void *](这里是一个句柄)
  4. 4 [+0x008] m_dwFlags : 0xd [Type: Volatile<unsigned long>]

            既然是一个 handle,我们就使用【!handle】命令查看一下就知道了。

  1. 1 0:007> !handle 0x2d0 f
  2. 2 Handle 2d0
  3. 3 Type Event
  4. 4 Attributes 0
  5. 5 GrantedAccess 0x1f0003:
  6. 6 Delete,ReadControl,WriteDac,WriteOwner,Synch
  7. 7 QueryState,ModifyState
  8. 8 HandleCount 2
  9. 9 PointerCount 65537
  10. 10 Name <none>
  11. 11 Object Specific Information
  12. 12 Event Type Auto Reset(其实就是 AutoResetEvent
  13. 13 Event is Waiting


        2.6、我们看看 ThinLock 锁的实现逻辑。
            调试源码:Example_14_1_5
            我们编译项目,打开 Windbg,点击【文件】----》【launch executable】附加程序,打开调试器的界面,程序已经处于中断状态。我们需要使用【g】命令,继续运行程序,我们的控制台会输出:3 已进入 Person 锁中。此时,我们的 Windbg 处于 int 3 中断的状态,就可以调试程序了。
            我们还是先使用【!syncblk】命令,查看一下同步块。

  1. 1 0:009> !syncblk
  2. 2 Index SyncBlock MonitorHeld Recursion Owning Thread Info SyncBlock Owner
  3. 3 -----------------------------
  4. 4 Total 6
  5. 5 CCW 1
  6. 6 RCW 2
  7. 7 ComClassFactory 0
  8. 8 Free 0

            没有同步块,这就说明虽然用到了锁,但是没有用到同步块。我们既然想要查看这个Person对象的对象,那我们就现在托管堆中找到这个对象,可以使用【!dumpheap -type Person】命令,完成这个操作。

  1. 1 0:009> !dumpheap -type Person
  2. 2 Address MT Size
  3. 3 02c224d4 01224e0c 12
  4. 4
  5. 5 Statistics:
  6. 6 MT Count TotalSize Class Name
  7. 7 01224e0c 1 12 Example_14_1_5.Person
  8. 8 Total 1 objects

            红色标注的地址就是 Person 对象的地址。我们可以使用【!dp】命令来查看。

  1. 1 0:009> dp 02c224d4-0x4 l4
  2. 2 02c224d0 00000003 01224e0c 00000000 00000000

            同步块索引的值是3(00000003),这个3 就是持有锁的线程 id 值。我们可以使用【!t】或者【!threads】命令查看一下当前的线程。

  1. 1 0:009> !t
  2. 2 ThreadCount: 4
  3. 3 UnstartedThread: 0
  4. 4 BackgroundThread: 3
  5. 5 PendingThread: 0
  6. 6 DeadThread: 0
  7. 7 Hosted Runtime: no
  8. 8 Lock
  9. 9 ID OSID ThreadOBJ State GC Mode GC Alloc Context Domain Count Apt Exception
  10. 10 0 1 3b00 00cb9760 2a020 Preemptive 02C2A044:00000000 00c823e8 1 MTA
  11. 11 5 2 174c 00c893b8 2b220 Preemptive 00000000:00000000 00c823e8 0 MTA (Finalizer)
  12. 12 9 3 29fc 00cf0fe8 1029220 Preemptive 02C2742C:00000000 00c823e8 1 MTA (Threadpool Worker) (这个就是持有锁的线程,id=3
  13. 13 11 4 37a8 00cf4af8 1029220 Preemptive 02C281E8:00000000 00c823e8 0 MTA (Threadpool Worker)

            我们知道了线程 id,我们就可以切换到该线程上去看看那调用栈是什么样子的。

  1. 1 0:003> ~~[29fc]s
  2. 2 eax=0567f124 ebx=00000000 ecx=00cf0fe8 edx=0567f55c esi=02c27244 edi=0567f168
  3. 3 eip=7599f262 esp=0567f0bc ebp=0567f148 iopl=0 nv up ei pl zr na pe nc
  4. 4 cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000244
  5. 5 KERNELBASE!wil::details::DebugBreak+0x2:
  6. 6 7599f262 cc int 3

            然后,我们看看线程栈。

  1. 1 0:009> !clrstack
  2. 2 OS Thread Id: 0x29fc (9)
  3. 3 Child SP IP Call Site
  4. 4 0567f0d4 7599f262 [HelperMethodFrame: 0567f0d4] System.Diagnostics.Debugger.BreakInternal()
  5. 5 0567f150 6f7cf195 System.Diagnostics.Debugger.Break() [f:\dd\ndp\clr\src\BCL\system\diagnostics\debugger.cs @ 91]
  6. 6 0567f178 012b0a90 Example_14_1_5.Program+c.b__1_0() [E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\Example_14_1_5\Program.cs @ 18]
  7. 7 0567f1c0 6f09d4bb System.Threading.Tasks.Task.InnerInvoke() [f:\dd\ndp\clr\src\BCL\system\threading\Tasks\Task.cs @ 2884]
  8. 8 0567f1cc 6f09b731 System.Threading.Tasks.Task.Execute() [f:\dd\ndp\clr\src\BCL\system\threading\Tasks\Task.cs @ 2498]
  9. 9 0567f1f0 6f09b6fc System.Threading.Tasks.Task.ExecutionContextCallback(System.Object) [f:\dd\ndp\clr\src\BCL\system\threading\Tasks\Task.cs @ 2861]
  10. 10 0567f1f4 6f038604 System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean) [f:\dd\ndp\clr\src\BCL\system\threading\executioncontext.cs @ 980]
  11. 11 0567f260 6f038537 System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean) [f:\dd\ndp\clr\src\BCL\system\threading\executioncontext.cs @ 928]
  12. 12 0567f274 6f09b4b2 System.Threading.Tasks.Task.ExecuteWithThreadLocal(System.Threading.Tasks.Task ByRef) [f:\dd\ndp\clr\src\BCL\system\threading\Tasks\Task.cs @ 2827]
  13. 13 0567f2d8 6f09b357 System.Threading.Tasks.Task.ExecuteEntry(Boolean) [f:\dd\ndp\clr\src\BCL\system\threading\Tasks\Task.cs @ 2767]
  14. 14 0567f2e8 6f09b29d System.Threading.Tasks.Task.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem() [f:\dd\ndp\clr\src\BCL\system\threading\Tasks\Task.cs @ 2704]
  15. 15 0567f2ec 6f00eb7d System.Threading.ThreadPoolWorkQueue.Dispatch() [f:\dd\ndp\clr\src\BCL\system\threading\threadpool.cs @ 820]
  16. 16 0567f33c 6f00e9db System.Threading._ThreadPoolWaitCallback.PerformWaitCallback() [f:\dd\ndp\clr\src\BCL\system\threading\threadpool.cs @ 1161]
  17. 17 0567f55c 70def036 [DebuggerU2MCatchHandlerFrame: 0567f55c]

            红色标注的就是我们程序暂停的位置,VisualStudio 所对应的代码行数。
            其实,我们获取到了对象地址,可以【!do】一下,也可以看到一些信息。

  1. 1 0:009> !do 02c224d4
  2. 2 Name: Example_14_1_5.Person
  3. 3 MethodTable: 01224e0c
  4. 4 EEClass: 0122135c
  5. 5 Size: 12(0xc) bytes
  6. 6 File: E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\Example_14_1_5\bin\Debug\Example_14_1_5.exe
  7. 7 Fields:
  8. 8 None
  9. 9 ThinLock owner 3 (00cf0fe8), Recursive 0



四、总结
    终于写完了。还是老话,虽然很忙,写作过程也挺累的,但是看到了自己的成长,心里还是挺快乐的。学习过程真的没那么轻松,还好是自己比较喜欢这一行,否则真不知道自己能不能坚持下来。老话重谈,《高级调试》的这本书第一遍看,真的很晕,第二遍稍微好点,不学不知道,一学吓一跳,自己欠缺的很多。好了,不说了,不忘初心,继续努力,希望老天不要辜负努力的人。

原文链接:https://www.cnblogs.com/PatrickLiu/p/17910805.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号