lock语句是设置锁定和解除锁定的一种简单方式。
开启20个任务,每个任务对相同的初始值为0的字段各加一50000次,理论上的结果为20*50000 = 1000000。但是,多次运行该程序几乎得不到正确的结果。
方法DoTheJob及其在IL语言中的代码如下:
public void DoTheJob() { for (int i = 0; i < 50000; i++) { _sharedState.State += 1; } } .method public hidebysig instance void DoTheJob() cil managed { // 代码大小 46 (0x2e) // 该方法使用大栈的大小 .maxstack 3 // 定义局部变量 // 定义了一个int32类型的局部变量V_0,索引为0 // 定义了一个bool 类型的局部变量V_1,索引为1 .locals init (int32 V_0, bool V_1) // 如果修补操作码,则填充空间。尽管可能消耗处理周期,但未执行任何有意义的操作 IL_0000: nop // 将整数值0作为int32推送到评估堆栈上 // 即:i的初始值 IL_0001: ldc.i4.0 // 从评估堆栈的顶部弹出当前值并将其存储到索引为0的句柄变量量表中 // 即:i = 0 IL_0002: stloc.0 // 无条件地将控制转移到目标指令(短格式) // 即:跳转执行 i< 50000 IL_0003: br.s IL_0021 // 如果修补操作码,则填充空间。 // 尽管可能消耗处理周期,但未执行任何有意义的操作 IL_0005: nop IL_0006: nop // 将索引为 0 的变量的值加载到评估堆栈上 IL_0007: ldarg.0 // 查找对象中其引用当前位于评估堆栈的字段的值 IL_0008: ldfld class SynchronizatonSamples.SharedState SynchronizatonSamples.Job::_sharedState // 复制评估堆栈上当前最顶端的值,然后将副本推送到评估堆栈上 IL_000d: dup // 对对象调用后期绑定方法,并且将返回值推送到评估堆栈上 IL_000e: callvirt instance int32 SynchronizatonSamples.SharedState::get_State() // 将整数值 1 作为 int32 推送到评估堆栈上 IL_0013: ldc.i4.1 // 将两个值相加并将结果推送到评估堆栈上 IL_0014: add // 对对象调用后期绑定方法,并且将返回值推送到评估堆栈上 IL_0015: callvirt instance void SynchronizatonSamples.SharedState::set_State(int32) // 如果修补操作码,则填充空间。尽管可能消耗处理周期,但未执行任何有意义的操作 IL_001a: nop IL_001b: nop IL_001c: nop // 将索引 0 处的局部变量加载到评估堆栈上 IL_001d: ldloc.0 // 将整数值 1 作为 int32 推送到评估堆栈上 IL_001e: ldc.i4.1 // 将两个值相加并将结果推送到评估堆栈上 IL_001f: add 从评估堆栈的顶部弹出当前值并将其存储到索引 0 处的局部变量列表中 IL_0020: stloc.0 // 将索引 0 处的局部变量加载到评估堆栈上 IL_0021: ldloc.0 // 将所提供的 int32 类型的值作为 int32 推送到评估堆栈上 IL_0022: ldc.i4 0xc350 // 比较两个值。如果第一个值小于第二个值,则将整数值 1 (int32) 推送到评估堆栈上; // 反之,将 0 (int32) 推送到评估堆栈上 // 即:i < 50000 IL_0027: clt // 从评估堆栈的顶部弹出当前值并将其存储到索引 1 处的局部变量列表中 IL_0029: stloc.1 // 将索引 1 处的局部变量加载到评估堆栈上 IL_002a: ldloc.1 // 如果 value 为 true、非空或非零,则将控制转移到目标指令(短格式) IL_002b: brtrue.s IL_0005 // 从当前方法返回,并将返回值(如果存在)从调用方的评估堆栈推送到被调用方的评估堆栈上 IL_002d: ret } // end of method Job::DoTheJob程序先获取变量_sharedState.State的当前值。执行计算(+1)操作,最后将计算后的值赋值给变量_sharedState.State。
如果有一个线程A执行到(+1)操作,但还未给变量赋值,线程发生了切换。假定在切换的瞬间变量_sharedState.State的值为100,+1操作后的值为101,因为发生了线程切换变量_sharedState.State的值保持100不变,+1操作的值101,进行临时存储。新的线程B判断当前变量_sharedState.State的值为100,并对其执行新的+1操作,结果变量_sharedState.State的值变为101。经过多次的线程切换,变量_sharedState.State的值增加为200。在某一时刻被暂定的线程A又重新获取时间片,并开始执行。由于之前的+1操作已经完成,仅剩下给变量赋值。继续执行会将101赋值给变量_sharedState.State。此时,变量的值由原先的200变成了101。
IL语言基本知识
https://docs.microsoft.com/zh-tw/previous-versions/dd229210(v=msdn.10)?redirectedfrom=MSDN
IL语言指令表
https://www.cnblogs.com/yinrq/p/5485630.html
namespace SynchronizatonSamples { public class SharedState { public int State { get; set; } } public class Job { private SharedState _sharedState; public Job(SharedState sharedState) => _sharedState = sharedState; public void DoTheJob() { for (int i = 0; i < 50000; i++) { _sharedState.State += 1; } } } class Program { static void Main() { // 创建多任务共享的字段 var state = new SharedState(); // 定义任务的数量 var tasks = new System.Threading.Tasks.Task[20]; for (int i = 0; i < tasks.Length; i++) // 虽然使用的是不同的Job实例,但传入的是相同的字段 tasks[i] = System.Threading.Tasks.Task.Run(() => new Job(state).DoTheJob()); // 等待所有任务执行完成 System.Threading.Tasks.Task.WaitAll(tasks); // 输出多任务的执行结果 System.Console.WriteLine($"summarized {state.State}"); } } }为了避免上述错误,可以通过使用lock关键字,对使用共享字段进行加锁。一个线程获取到锁之后,直到其释放锁,其他线程才能进行方位,否则只能等待。
namespace SynchronizatonSamples { public class SharedState { public int State { get; set; } } public class Job { private SharedState _sharedState; public Job(SharedState sharedState) => _sharedState = sharedState; public void DoTheJob() { for (int i = 0; i < 50000; i++) { // 多个任务使用相同的字段,可以锁定所有任务 lock (_sharedState) { _sharedState.State += 1; } } } } class Program { static void Main() { // 创建多任务共享的字段 var state = new SharedState(); // 定义任务的数量 var tasks = new System.Threading.Tasks.Task[20]; for (int i = 0; i < tasks.Length; i++) // 虽然使用的是不同的Job实例,但传入的是相同的字段 tasks[i] = System.Threading.Tasks.Task.Run(() => new Job(state).DoTheJob()); // 等待所有任务执行完成 System.Threading.Tasks.Task.WaitAll(tasks); // 输出多任务的执行结果 System.Console.WriteLine($"summarized {state.State}"); } } }可以把锁放在object类型或静态成员:
lock (typeof(StaticClass)) { }一次只有一个线程能访问相同实例的方法
public class Demo { // Only one thread at a time can access the DoThis and DoThat methods public void DoThis() { lock(this) { } } public void DoThat() { lock(this) { } } }Interlocked类用于使变量的简单语句原子化。i++不是线程安全的,它的操作包括从内存中获取一个值,给该值递增1,再将它存储回内存。这些操作都可能会被线程调度器打断。Interlocked类提供了以线程安全的方式递增、递减、交换和读取值的方法。
与其他同步技术相比,使用Interlocked类会快很多。但是,它只能用于简单的同步问题。
lock(this) { if(_someState == null) { _someState = newState; } } =====>>>> Interlocked.CompareExchange<SomeState>(ref someState, newState, null); public int State { get { lock(this) { return ++_state; } } } =====>>>> public int State { get { return Interlocked.Increment(ref _state); } }lock语句由编译器解析为使用Monitor类。
lock(obj) { // ... } =====>>>> Monitor.Enter(obj); try { // ... } finally { Monitor.Exit(obj); }调用Enter()方法,该方法会一直等待,直到线程锁定对象为止。一次只有一个线程能锁定对象。只要解除了锁定,线程就可以进入同步阶段。Monitor类的Exit方法解除了锁定。
与lock语句相比,Monitor类的主要优点是:可以添加一个等待被锁定的超时值。这样就不会无限期地等待被锁定,而可以像下面的例子那样使用TryEnter()方法,其中给它传递一个超时值,指定等待被锁定的最长时间。如果obj被锁定,TryEmter()方法就把布尔类型的引用参数设置未true。如果另一个线程锁定obj的事件超过了500毫秒,TryEnter()方法就把变量lockTaken设置为false,线程不在等待,而是用于其他操作。也许在以后,该线程会尝试再次获得锁定。
bool _lockTaken = false; Monitor.TryEnter(_obj, 500, ref _lockTaken); if(_lockTaken) { try { // ... } finally { Monitor.Exit(obj); } } else { // didn't get the lock, do something else }如果基于对象的锁定(Monitor)的系统开销由于垃圾回收而过高,可以使用SpinLock结构。如果有大量的锁定,且锁定的时间总是非常短,SpinLock结构就很有用。应避免使用多个SpinLock结构,也不要调用任何可能阻塞的内容。
除了体系结构上的区别之外,SpinLock结构的用法非常类似与Monitor类。使用Enter或TryEnter方法获得锁定,使用Eixt方法释放锁定。SpinLock结构还提供了属性IsHeld和IsHeldByCurrentThread,指定它当前是否是锁定的。
WaitHandle用于等待一个信号。可以等待不同的信号,因为WaitHandle是一个基类。
namespace AsyncDelegate { public delegate int TakesAWhileDelegate(int x, int ms); class Program { static void Main() { try { TakesAWhileDelegate d1 = TakesAWhile; System.IAsyncResult ar = d1.BeginInvoke(1, 3000, null, null); while (true) { System.Console.Write("."); // 等待50ms,如果未等到信号,则停止等待 if (ar.AsyncWaitHandle.WaitOne(50)) { System.Console.WriteLine("Can get the result now"); break; } } int result = d1.EndInvoke(ar); System.Console.WriteLine($"result: {result}"); } catch (System.PlatformNotSupportedException) { System.Console.WriteLine("PlatformNotSupported exception - with async delegates please use the full .NET Framework"); } } public static int TakesAWhile(int x, int ms) { System.Threading.Tasks.Task.Delay(ms).Wait(); return 42; } } }可以利用BeginInvoke方法的返回值的AsyncWaitHandle属性访问WaitHandle基类。在调用WaitOne方法或者超时发生时,线程会等待接收一个与等待句柄相关的信号。调用EndInvoke方法,线程最终会阻塞,直到得到结果为止。
WaitOne - 等待一个信号、WaitAll - 等待必须获得所有信号WaitAny - 等待多个对象中的任意一个WaitAll与WaitAny时WaitHandle类的静态方法,接收一个WaitHandle数组。
Mutex是跨进程同步访问的一个类。它非常类似与Monitor类,因为它们都只有一个线程能拥有锁定。
在构造函数中,可以指定互斥是否最初应由主线程拥有,定义互斥的名称,获得互斥是否已存在的信息。在下面的示例代码中,第3个参数定义输入参数 ,接收一个表示互斥是否为新建的布尔值。如果返回值为false,就表示互斥已经定义。互斥可以在另一个进程中定义,因为操作系统能够识别由名称的互斥,它由不同的进程共享。如果没有给互斥指定名称,互斥就是未命名的,不在不同进程之间共享。
var mutex = new Mutex(false, "MutexName", out var createNew);要打开已有的互斥,还可以使用Mutex.OpenExisting方法。由于Mutex类派生自WaitHandle,因此可以利用WaitOne方法获得互斥锁定,在该过程中称为该互斥的拥有者。通过调用ReleaseMutex方法释放互斥。
if(mutex.WaitOne()) { try { // ... } finally { mutex.ReleaseMutex(); } } else { // ... }由于系统能识别有名称的互斥 ,因此可以使用它禁止程序启动两次。在下面的WPF应用程序中,调用Mutex对象的构造函数。接着验证指定名称的互斥是否存在。如果存在,退出应用。
public partial class App : Application { protected override void OnStartup(StartupEvenArgs e) { var mutex = new Mutex(false, "MutexName", out var mutexCreated); if(!mutexCreate) { MessageBox.Show("You can onlu start one instance of the application"); Application.Current.Shutdown(); } base.OnStartup(e); } }信号量非常类似与互斥,其区别是,信号量可以同时由多个线程使用。信号量是一种计数的互斥锁定。使用信号量,可以定义允许同时访问受旗语锁定保护的资源的线程个数。如果需要限制可以访问可使用资源的线程数,信号量就很有用。例如,如果系统有3个物理端口可用,就允许3个线程同时访问I/O端口,但第4个线程需要等待前3个线程中的一个释放资源。
Semaphore类可以命名,使用系统范围内的资源,允许在不同进程之间同步。
SemaphoreSlim类是简化版本,只能在一个进程内的不同线程之间同步。
public SemaphoreSlim(int initialCount, int maxCount); // initialCount - 在程序初始状态时,释放出了多少个可以直接被线程使用的资源 // maxCount - Semaphore所包含的所有资源的数量. 包括当前可用的资源,和当前尚未释放的资源 using System.Threading; using System.Threading.Tasks; using static System.Console; namespace SemaphoreSample { class Program { static void Main(string[] args) { int taskCount = 6; int semaphoreCount = 3; var semaphore = new SemaphoreSlim(semaphoreCount, semaphoreCount); var tasks = new Task[taskCount]; for (int i = 0; i < taskCount; i++) { tasks[i] = Task.Run(() => TaskMain(semaphore)); } Task.WaitAll(tasks); WriteLine("All tasks finished"); } private static void TaskMain(SemaphoreSlim semaphore) { bool isCompleted = false; while (!isCompleted) { if (semaphore.Wait(600)) { try { WriteLine($"Task {Task.CurrentId} locks the semaphore"); Task.Delay(2000).Wait(); } finally { WriteLine($"Task {Task.CurrentId} releases the semaphore"); semaphore.Release(); isCompleted = true; } } else { WriteLine($"Timeout for task {Task.CurrentId}; wait again"); } } } } }事件也是一个系统范围内的资源同步方法。
Wait() - 等待信号,当前线程处于阻塞状态Set() - 发送信号,解除阻塞线程的状态Reset() - 重置信号,线程再次调用Wait方法,会处于阻塞状态 ManualResetEvent - 接收到信号后,解除Wait方法的阻塞状态。直到调用Reset方法,不论执行多少次Wait方法,都不阻塞AutoResetEvent - 接收到信号后,解除Wait方法的阻塞状态。在下一次调用Wait方法时,重新处于阻塞状态 ManualResetEventSlim - ManualResetEvent 的简化版本CountdownEvent - 接收到指定次数,才解除阻塞对于AutoResetEvent,如果同时有多个线程处于等待状态。当接收到Set信号时,只有一个线程结束其等待状态,它不是等待时间最长的线程,而是优先级最高的线程。
namespace EventSample { class Program { static void Main() { const int taskCount = 4; var cEvent = new System.Threading.CountdownEvent(taskCount); var calcs = new Calculator[taskCount]; for (int i = 0; i < taskCount; i++) { calcs[i] = new Calculator(cEvent); int i1 = i; System.Threading.Tasks.Task.Run(() => calcs[i1].Calculation(i1 + 1, i1 + 3)); } cEvent.Wait(); System.Console.WriteLine("all finished"); for (int i = 0; i < taskCount; i++) { System.Console.WriteLine($"task for {i}, result: {calcs[i].Result}"); } } } public class Calculator { private System.Threading.CountdownEvent _cEvent; public int Result { get; private set; } public Calculator(System.Threading.CountdownEvent ev) { _cEvent = ev; } public void Calculation(int x, int y) { System.Console.WriteLine($"Task {System.Threading.Tasks.Task.CurrentId} starts calculation"); System.Threading.Tasks.Task.Delay(new System.Random().Next(3000)).Wait(); Result = x + y; // signal the event-completed! System.Console.WriteLine($"Task {System.Threading.Tasks.Task.CurrentId} is ready"); _cEvent.Signal(); } } }参考
https://blog.csdn.net/wangzhiyu1980/article/details/45688075
Barrier 是 .Net 提供的一直并发的机制,它允许多个任务同步不同阶段的并发工作,多任务按阶段进行同步。
这里的关键点是【多个任务】和【不同阶段】。
假设有4个相同的任务(Task),每个任务都有4个阶段(Phase),当他们并发工作时,只有当所有任务的相同步骤都完成时,所有任务才可以开始下一个步骤。
namespace MyBarrier { class Program { private static void Phase0Doing(int TaskID) => System.Console.WriteLine($"Task : #{TaskID} ===== Phase 0"); private static void Phase1Doing(int TaskID) => System.Console.WriteLine($"Task : #{TaskID} ***** Phase 1"); private static void Phase2Doing(int TaskID) => System.Console.WriteLine($"Task : #{TaskID} ^^^^^ Phase 2"); private static void Phase3Doing(int TaskID) => System.Console.WriteLine($"Task : #{TaskID} $$$$$ Phase 3"); private const int TaskNum = 4; private static readonly System.Threading.Tasks.Task[] _Tasks = new System.Threading.Tasks.Task[TaskNum]; private static System.Threading.Barrier _barrier = new System.Threading.Barrier(TaskNum, (barrier) => System.Console.WriteLine($"-- Current Phase:{_barrier.CurrentPhaseNumber} --")); static void Main(string[] args) { for (int i = 0; i < TaskNum; i++) { // 创建并启动四个任务,每个任务各执行四个阶段的任务 _Tasks[i] = System.Threading.Tasks.Task.Factory.StartNew((num) => { var taskid = (int)num; Phase0Doing(taskid); _barrier.SignalAndWait(); Phase1Doing(taskid); _barrier.SignalAndWait(); Phase2Doing(taskid); _barrier.SignalAndWait(); Phase3Doing(taskid); _barrier.SignalAndWait(); }, i); } var finalTask = System.Threading.Tasks.Task.Factory.ContinueWhenAll(_Tasks, (tasks) => { System.Threading.Tasks.Task.WaitAll(_Tasks); System.Console.WriteLine("========================================"); System.Console.WriteLine("All Phase is completed"); _barrier.Dispose(); }); finalTask.Wait(); System.Console.ReadLine(); } } }运行结果:
Task : #1 ===== Phase 0 Task : #0 ===== Phase 0 Task : #3 ===== Phase 0 Task : #2 ===== Phase 0 -- Current Phase:0 -- Task : #0 ***** Phase 1 Task : #3 ***** Phase 1 Task : #2 ***** Phase 1 Task : #1 ***** Phase 1 -- Current Phase:1 -- Task : #3 ^^^^^ Phase 2 Task : #1 ^^^^^ Phase 2 Task : #0 ^^^^^ Phase 2 Task : #2 ^^^^^ Phase 2 -- Current Phase:2 -- Task : #1 $$$$$ Phase 3 Task : #3 $$$$$ Phase 3 Task : #2 $$$$$ Phase 3 Task : #0 $$$$$ Phase 3 -- Current Phase:3 -- ======================================== All Phase is completed根据运行结果可知,在相同的阶段内运行的顺序不尽相同,但是在每个阶段都等到所有任务完成之后才开始下一个阶段的任务。
为了使锁定机制允许锁定多个读取器(而不是一个写入器)访问某个资源,可以使用ReaderWriterLockSlim类。这个类提供了一个锁定功能,如果没有写入器锁定资源,就允许多个读取器访问资源,但只能有一个写入器锁定该资源。
ReaderWriterLockSlim类有阻塞或不阻塞的方法来获取读取锁,如阻塞的EnterReadLock和不阻塞的TryEnterReadLock方法,还可以使用阻塞的EnterWriterLock和不阻塞的TryEnterWriteLock方法获得 写入锁定。如果任务先读取资源,之后写如资源,它就可以使用EnterUpgradableReadLock或TryEnterUpgradableReadLock方法获得可升级的读取锁定。有了这个锁定,就可以获得写入锁定给,而无需释放读取锁定。
下面的示例程序创建了一个包含6项的集合和一个ReaderWriteLockSlim对象。ReaderMethod方法获得一个读取锁定,读取列表中的所有项,并把它们写到控制台。WriteMethod方法试图获得一个写入锁定,已 改变集合的所有值。在Main方法中,启动6个任务,已调用ReaderMethod或WriteMethod方法 。
namespace ReaderWriterLockSample { class Program { private static System.Collections.Generic.List<int> _items = new System.Collections.Generic.List<int>() { 0, 1, 2, 3, 4, 5 }; private static System.Threading.ReaderWriterLockSlim _rwl = new System.Threading.ReaderWriterLockSlim(System.Threading.LockRecursionPolicy.SupportsRecursion); public static void ReaderMethod(object reader) { try { _rwl.EnterReadLock(); for (int i = 0; i < _items.Count; i++) { System.Console.WriteLine($"读取器({reader})获得锁定,输出集合值。 当前索引:{i}, 当前值:{_items[i]}"); System.Threading.Tasks.Task.Delay(40).Wait(); } } finally { _rwl.ExitReadLock(); } } public static void WriterMethod(object writer) { try { while (!_rwl.TryEnterWriteLock(50)) { System.Console.WriteLine($"写入器({writer}):等待获取写入锁定"); System.Console.WriteLine($"当前读取锁定的数量: {_rwl.CurrentReadCount}"); } System.Console.WriteLine($"写入器({writer}):获取到写入锁定"); for (int i = 0; i < _items.Count; i++) { _items[i]++; System.Threading.Tasks.Task.Delay(50).Wait(); } System.Console.WriteLine($"写入器({writer}):写入完成,即将释放写入锁定"); } finally { _rwl.ExitWriteLock(); } } static void Main() { System.Console.WriteLine($"程序启动 ..."); var taskFactory = new System.Threading.Tasks.TaskFactory(System.Threading.Tasks.TaskCreationOptions.LongRunning, System.Threading.Tasks.TaskContinuationOptions.None); var tasks = new System.Threading.Tasks.Task[5]; tasks[0] = taskFactory.StartNew(WriterMethod, 1); // 写1 tasks[1] = taskFactory.StartNew(ReaderMethod, 1); // 读1 tasks[2] = taskFactory.StartNew(ReaderMethod, 2); // 读2 tasks[3] = taskFactory.StartNew(WriterMethod, 2); // 写2 tasks[4] = taskFactory.StartNew(ReaderMethod, 3); // 读3 System.Threading.Tasks.Task.WaitAll(tasks); } } }程序运行结果如下:
程序启动后,三个读取锁先后获得锁定,开始遍历集合并输出。在三个读取器执行的过程中,写入锁定处于等待状态。当三个读取操作完成之后,写入才获取到锁定。此时对集合集合的元素+1。同时,可以观察到写入1获取到锁定之后,写入2依然处于等待状态。当写入1完成之后,写入2才能获取到锁定。
程序启动 ... 读取器(3)获得锁定,输出集合值。 当前索引:0, 当前值:0 读取器(1)获得锁定,输出集合值。 当前索引:0, 当前值:0 读取器(2)获得锁定,输出集合值。 当前索引:0, 当前值:0 读取器(2)获得锁定,输出集合值。 当前索引:1, 当前值:1 读取器(1)获得锁定,输出集合值。 当前索引:1, 当前值:1 读取器(3)获得锁定,输出集合值。 当前索引:1, 当前值:1 写入器(1):等待获取写入锁定 当前读取锁定的数量: 3 写入器(2):等待获取写入锁定 当前读取锁定的数量: 3 读取器(2)获得锁定,输出集合值。 当前索引:2, 当前值:2 读取器(3)获得锁定,输出集合值。 当前索引:2, 当前值:2 读取器(1)获得锁定,输出集合值。 当前索引:2, 当前值:2 写入器(1):等待获取写入锁定 当前读取锁定的数量: 3 写入器(2):等待获取写入锁定 当前读取锁定的数量: 3 读取器(1)获得锁定,输出集合值。 当前索引:3, 当前值:3 读取器(2)获得锁定,输出集合值。 当前索引:3, 当前值:3 读取器(3)获得锁定,输出集合值。 当前索引:3, 当前值:3 写入器(1):等待获取写入锁定 当前读取锁定的数量: 3 写入器(2):等待获取写入锁定 当前读取锁定的数量: 3 读取器(1)获得锁定,输出集合值。 当前索引:4, 当前值:4 读取器(2)获得锁定,输出集合值。 当前索引:4, 当前值:4 读取器(3)获得锁定,输出集合值。 当前索引:4, 当前值:4 写入器(1):等待获取写入锁定 当前读取锁定的数量: 3 写入器(2):等待获取写入锁定 当前读取锁定的数量: 3 读取器(3)获得锁定,输出集合值。 当前索引:5, 当前值:5 读取器(1)获得锁定,输出集合值。 当前索引:5, 当前值:5 读取器(2)获得锁定,输出集合值。 当前索引:5, 当前值:5 写入器(1):等待获取写入锁定 当前读取锁定的数量: 3 写入器(2):等待获取写入锁定 当前读取锁定的数量: 3 写入器(1):获取到写入锁定 写入器(2):等待获取写入锁定 当前读取锁定的数量: 0 写入器(2):等待获取写入锁定 当前读取锁定的数量: 0 写入器(2):等待获取写入锁定 当前读取锁定的数量: 0 写入器(2):等待获取写入锁定 当前读取锁定的数量: 0 写入器(2):等待获取写入锁定 当前读取锁定的数量: 0 写入器(2):等待获取写入锁定 当前读取锁定的数量: 0 写入器(2):等待获取写入锁定 当前读取锁定的数量: 0 写入器(1):写入完成,即将释放写入锁定 写入器(2):获取到写入锁定 写入器(2):写入完成,即将释放写入锁定再次运行程序:
此时,程序开始运行后,写入1获得到锁定。写入2与读取操作均处于等待状态。写入1完成操作,释放锁定后,写入2获得锁定,读取操作继续处于等待状态。直到写入2完成,并释放锁定之后,读取操作才获取到锁定。
程序启动 ... 写入器(1):获取到写入锁定 写入器(2):等待获取写入锁定 当前读取锁定的数量: 0 写入器(2):等待获取写入锁定 当前读取锁定的数量: 0 写入器(2):等待获取写入锁定 当前读取锁定的数量: 0 写入器(2):等待获取写入锁定 当前读取锁定的数量: 0 写入器(2):等待获取写入锁定 当前读取锁定的数量: 0 写入器(2):等待获取写入锁定 当前读取锁定的数量: 0 写入器(2):等待获取写入锁定 当前读取锁定的数量: 0 写入器(1):写入完成,即将释放写入锁定 写入器(2):获取到写入锁定 写入器(2):写入完成,即将释放写入锁定 读取器(2)获得锁定,输出集合值。 当前索引:0, 当前值:2 读取器(3)获得锁定,输出集合值。 当前索引:0, 当前值:2 读取器(1)获得锁定,输出集合值。 当前索引:0, 当前值:2 读取器(1)获得锁定,输出集合值。 当前索引:1, 当前值:3 读取器(3)获得锁定,输出集合值。 当前索引:1, 当前值:3 读取器(2)获得锁定,输出集合值。 当前索引:1, 当前值:3 读取器(2)获得锁定,输出集合值。 当前索引:2, 当前值:4 读取器(3)获得锁定,输出集合值。 当前索引:2, 当前值:4 读取器(1)获得锁定,输出集合值。 当前索引:2, 当前值:4 读取器(1)获得锁定,输出集合值。 当前索引:3, 当前值:5 读取器(3)获得锁定,输出集合值。 当前索引:3, 当前值:5 读取器(2)获得锁定,输出集合值。 当前索引:3, 当前值:5 读取器(2)获得锁定,输出集合值。 当前索引:4, 当前值:6 读取器(3)获得锁定,输出集合值。 当前索引:4, 当前值:6 读取器(1)获得锁定,输出集合值。 当前索引:4, 当前值:6 读取器(2)获得锁定,输出集合值。 当前索引:5, 当前值:7 读取器(3)获得锁定,输出集合值。 当前索引:5, 当前值:7 读取器(1)获得锁定,输出集合值。 当前索引:5, 当前值:7