一、介绍
    今天是《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 using System;
2 using System.Threading;
3
4 namespace Example_14_1_1
5 {
6 internal class Program
7 {
8 static void Main(string[] args)
9 {
10 var thread = new Thread(() =>
11 {
12 Console.WriteLine($"tid={Environment.CurrentManagedThreadId}");
13 Console.ReadLine();
14 });
15
16 thread.Start();
17
18 Console.ReadLine();
19 }
20 }
21 }

        1.2、Example_14_1_2

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

        1.3、Example_14_1_3

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

        1.4、Example_14_1_4(Net 7.0)

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

        1.5、Example_14_1_5

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

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

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

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

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

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

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

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

 1 0:004> !t
2 ThreadCount: 3
3 UnstartedThread: 0
4 BackgroundThread: 1
5 PendingThread: 0
6 DeadThread: 0
7 Hosted Runtime: no
8 Lock
9 ID OSID ThreadOBJ State GC Mode GC Alloc Context Domain Count Apt Exception
10 0 1 4b4 00696b10 2a020 Preemptive 02506254:00000000 006903d0 1 MTA
11 2 2 3ab8 00698df8 2b220 Preemptive 00000000:00000000 006903d0 0 MTA (Finalizer)
12 3 3 318c 006ee308 202b020 Preemptive 0250501C:00000000 006903d0 0 MTA
13 0:004> !threads
14 ThreadCount: 3
15 UnstartedThread: 0
16 BackgroundThread: 1
17 PendingThread: 0
18 DeadThread: 0
19 Hosted Runtime: no
20 Lock
21 ID OSID ThreadOBJ State GC Mode GC Alloc Context Domain Count Apt Exception
22 0 1 4b4 00696b10 2a020 Preemptive 02506254:00000000 006903d0 1 MTA
23 2 2 3ab8 00698df8 2b220 Preemptive 00000000:00000000 006903d0 0 MTA (Finalizer)
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:000> !dumpheap -type ManualResetEvent
2 Address MT Size
3 033e24d4 6d53d578 24
4
5 Statistics:
6 MT Count TotalSize Class Name
7 6d53d578 1 24 System.Threading.ManualResetEvent
8 Total 1 objects

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

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

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

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

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

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

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

 1 1:000> !handle 2f8 f
2 Handle 2f8
3 Type Event
4 Attributes 0
5 GrantedAccess 0x1f0003:
6 Delete,ReadControl,WriteDac,WriteOwner,Synch
7 QueryState,ModifyState
8 HandleCount 2
9 PointerCount 65535
10 Name <none>
11 Object Specific Information
12 Event Type Manual Reset
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】命令。

0:000> !dumpheap -type ManualResetEvent
Address MT Size
033224d4 6d53d578 24 Statistics:
MT Count TotalSize Class Name
6d53d578 1 24 System.Threading.ManualResetEvent
Total 1 objects

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

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

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

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

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

1 lkd> dp 0xFFFF9004B7B916E0 l1
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 0:000> !dumpheap -type Semaphore
2 Address MT Size
3 02f924d4 6d59611c 24
4
5 Statistics:
6 MT Count TotalSize Class Name
7 6d59611c 1 24 System.Threading.Semaphore
8 Total 1 objects

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

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

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

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

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

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

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

 1 0:000> !handle 314 f
2 Handle 314
3 Type Semaphore
4 Attributes 0
5 GrantedAccess 0x1f0003:
6 Delete,ReadControl,WriteDac,WriteOwner,Synch
7 QueryState,ModifyState
8 HandleCount 2
9 PointerCount 65534
10 Name <none>
11 Object Specific Information
12 Semaphore Count 4(又增加了一次)
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 lkd> dp 0xFFFFCE09E477EA60 l1
2 ffffce09`e477ea60 00000004`00080005

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

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

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

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

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

 1 0:000> g
2 ModLoad: 058f0000 059ea000 image058f0000
3 ModLoad: 059f0000 05aea000 image059f0000
4 (52c.2990): CLR exception - code e0434352 (first chance)
5 ModLoad: 66bf0000 66cf5000 C:\Windows\Microsoft.NET\Framework\v4.0.30319\diasymreader.dll
6 ModLoad: 6cd30000 6d548000 C:\Windows\assembly\NativeImages_v4.0.30319_32\System.Core\System.Core.ni.dll
7 ModLoad: 73630000 73643000 C:\Windows\SysWOW64\CRYPTSP.dll
8 ModLoad: 73600000 7362f000 C:\Windows\SysWOW64\rsaenh.dll
9 ModLoad: 75ce0000 75cf9000 C:\Windows\SysWOW64\bcrypt.dll
10 ModLoad: 73690000 7369a000 C:\Windows\SysWOW64\CRYPTBASE.dll
11 (52c.2990): CLR exception - code e0434352 (!!! second chance !!!)
12 *** WARNING: Unable to verify checksum for C:\Windows\assembly\NativeImages_v4.0.30319_32\\System.ni.dll
13 eax=00f3ec58 ebx=00000005 ecx=00000005 edx=00000000 esi=00f3ed1c edi=00000001
14 eip=75969862 esp=00f3ec58 ebp=00f3ecb4 iopl=0 nv up ei pl nz ac po nc
15 cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000212
16 KERNELBASE!RaiseException+0x62:
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 0:007> !syncblk
2 Index SyncBlock   MonitorHeld   Recursion   Owning Thread Info SyncBlock Owner
3 9 0000026C988CE368   3   1   000002AD2EE66800 18b0 7 0000026c9cc0cb88 Example_14_1_4_Core.Person
4 -----------------------------
5 Total 12
6 CCW 0
7 RCW 0
8 ComClassFactory 0
9 Free 0

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            然后,我们看看线程栈。

 1 0:009> !clrstack
2 OS Thread Id: 0x29fc (9)
3 Child SP IP Call Site
4 0567f0d4 7599f262 [HelperMethodFrame: 0567f0d4] System.Diagnostics.Debugger.BreakInternal()
5 0567f150 6f7cf195 System.Diagnostics.Debugger.Break() [f:\dd\ndp\clr\src\BCL\system\diagnostics\debugger.cs @ 91]
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 0567f1c0 6f09d4bb System.Threading.Tasks.Task.InnerInvoke() [f:\dd\ndp\clr\src\BCL\system\threading\Tasks\Task.cs @ 2884]
8 0567f1cc 6f09b731 System.Threading.Tasks.Task.Execute() [f:\dd\ndp\clr\src\BCL\system\threading\Tasks\Task.cs @ 2498]
9 0567f1f0 6f09b6fc System.Threading.Tasks.Task.ExecutionContextCallback(System.Object) [f:\dd\ndp\clr\src\BCL\system\threading\Tasks\Task.cs @ 2861]
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 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 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 0567f2d8 6f09b357 System.Threading.Tasks.Task.ExecuteEntry(Boolean) [f:\dd\ndp\clr\src\BCL\system\threading\Tasks\Task.cs @ 2767]
14 0567f2e8 6f09b29d System.Threading.Tasks.Task.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem() [f:\dd\ndp\clr\src\BCL\system\threading\Tasks\Task.cs @ 2704]
15 0567f2ec 6f00eb7d System.Threading.ThreadPoolWorkQueue.Dispatch() [f:\dd\ndp\clr\src\BCL\system\threading\threadpool.cs @ 820]
16 0567f33c 6f00e9db System.Threading._ThreadPoolWaitCallback.PerformWaitCallback() [f:\dd\ndp\clr\src\BCL\system\threading\threadpool.cs @ 1161]
17 0567f55c 70def036 [DebuggerU2MCatchHandlerFrame: 0567f55c]

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

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

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

Net 高级调试之十四:线程同步的基础知识和常见的同步原语的更多相关文章

  1. 十四、css动画基础知识

    引用动画的方式:   1.轻量动画: cubic-bezier(0.165, 0.840, 0.440, 1.000);//加上贝塞尔曲线使动画运动起来更加平滑 2..scrollNews,.m-tr ...

  2. 超详细的Java面试题总结(四 )之JavaWeb基础知识总结

    系列文章请查看: 超详细的Java面试题总结(一)之Java基础知识篇 超详细的Java面试题总结(二)之Java基础知识篇 超详细的Java面试题总结(三)之Java集合篇常见问题 超详细的Java ...

  3. [系统安全] 十六.PE文件逆向基础知识(PE解析、PE编辑工具和PE修改)

    [系统安全] 十六.PE文件逆向基础知识(PE解析.PE编辑工具和PE修改) 文章来源:https://masterxsec.github.io/2017/05/02/PE%E6%96%87%E4%B ...

  4. 【数据库上】 第四讲 E-R模型基础知识

    第四讲 E-R模型基础知识 一.数据库设计过程 数据库设计的关键阶段? 各个阶段设计的主要任务? 基础条件:清楚一个应用系统的功能需求与数据需求(直接与用户交互.数据流程图示例/UML类图等) 核心阶 ...

  5. JAVA基础知识总结13(同步)

    好处:解决了线程安全问题. 弊端:相对降低性能,因为判断锁需要消耗资源,还容易产生了死锁. 定义同步是有前提的: 1,必须要有两个或者两个以上的线程,才需要同步. 2,多个线程必须保证使用的是同一个锁 ...

  6. JAVA基础知识(12)-----同步

    好处:解决了线程安全问题.弊端:相对降低性能,因为判断锁需要消耗资源,产生了死锁.定义同步是有前提的:1,必须要有两个或者两个以上的线程,才需要同步.2,多个线程必须保证使用的是同一个锁. 同步的第二 ...

  7. 《UNIX环境高级编程》(APUE) 笔记第一章 - UNIX基础知识

    1 - UNIX基础知识 Github 地址 1. 操作系统 可将操作系统定义为一种软件,它控制计算机硬件资源,提供程序运行环境.通常将这种软件称为 内核 (kernel) .( Linux 是 GN ...

  8. 第二十九节:Java基础知识-类,多态,Object,数组和字符串

    前言 Java基础知识-类,多态,Object,数组和字符串,回顾,继承,类的多态性,多态,向上转型和向下转型,Object,数组,多维数组,字符串,字符串比较. 回顾 类的定义格式: [类的修饰符] ...

  9. Appium+python自动化(十五)- Android 这些基础知识,你知多少???(超详解)

    简介 前边具体操作和实战已经讲解和分享了很多了,但是一些android的一些基础知识,你又知道多少了,你都掌握了吗?这篇就由宏哥给小伙伴们既是一个分享,又是对前边的一次总结.为什么要对这些做一个简单的 ...

  10. [jsp学习笔记] jsp基础知识 数据初始化、同步

随机推荐

  1. 有Root与无Root安装git-lfs

    有Root与无Root安装git-lfs 直接安装 先查看arm还是AMD 例如当前使用Rocky Linux 8.8版本的内核.因此,应该下载适用于Rocky Linux 8.x的Git LFS安装 ...

  2. 主动写入流对@ResponseBody注解的影响

    问题回溯 2023年Q2某日运营反馈一个问题,商品系统商家中心某批量工具模板无法下载,导致功能无法使用(因为模板是动态变化的) 商家中心报错(JSON串): {"code":-1, ...

  3. Spring Boot 目录遍历--表达式注入--代码执行--(CVE-2021-21234)&&(CVE-2022-22963)&&(CVE-2022-22947)&&(CVE-2022-2296)

    Spring Boot 目录遍历--表达式注入--代码执行--(CVE-2021-21234)&&(CVE-2022-22963)&&(CVE-2022-22947)& ...

  4. JAVA动态增强一个BaseController的已经存在的接口

    使用场景 前提场景 我们多个系统同时继承了某一个通用系统,通用系统的接口是不会允许随意改变的,其他子系统都依赖于Base系统的通用接口 目标需求场景 但是有一个业务,需要给某一个公共接口增加子系统独有 ...

  5. Docker V24 及 Docker Compose V2 的安装及使用

    前言 Docker 是一款流行的开源容器化平台,使用 Docker 可以有效地隔离应用程序和系统环境,使得应用程序在不同的环境中具有相同的行为 Docker Compose 是一个用于定义和管理多个 ...

  6. Python socket实现ftp文件下载服务

    简要 使用Python socket和多线程实现一个FTP服务下载.下面的示例是固定下载某一个任意格式文件. 仅仅为了展示如果使用socket和多线程进行文件下载 服务端代码 import socke ...

  7. Rethinking Point Cloud Registration as Masking and Reconstruction论文阅读

    Rethinking Point Cloud Registration as Masking and Reconstruction 2023 ICCV *Guangyan Chen, Meiling ...

  8. TOP GP 把已经编译的per反编回对应版本的4fd(画面档)

    由于GP5.1,5.2,5.3的genero对应版本画面档开发互不兼容,下面提供各版本之间互转的操作方法: xshell切换到要反编译的per档目录,执行以下命令,就会在同目录下生成对应4fd档资料 ...

  9. umich cv-2-2

    UMICH CV Linear Classifiers 在上一篇博文中,我们讨论了利用损失函数来判断一个权重矩阵的好坏,在这节中我们将讨论如何去找到最优的权重矩阵 想象我们要下到一个峡谷的底部,我们自 ...

  10. 【学习】fhq-treap

    fhq-treap 是一种好写.复杂度低,且功能的优秀数据结构,涵盖了 treap 几乎所有的功能,其巧妙之处,就在于运用分离和合并两种操作代替了旋转操作. 1. BST 的定义 (摘自 OI Wik ...