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(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方法,线程最终会阻塞,直到得到结果为止。
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方法,会处于阻塞状态
对于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 _items = new System.Collections.Generic.List() { 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