lock語句
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語言指令表
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))
{
}
lock(this)
一次只有一個線程能訪問相同實例的方法
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
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);
}
}
Monitor
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
}
SpinLock
如果基於對象的鎖定(Monitor)的系統開銷由於垃圾回收而過高,可以使用SpinLock結構。如果有大量的鎖定,且鎖定的時間總是非常短,SpinLock結構就很有用。應避免使用多個SpinLock結構,也不要調用任何可能阻塞的內容。
除了體系結構上的區別之外,SpinLock結構的用法非常類似與Monitor類。使用Enter或TryEnter方法獲得鎖定,使用Eixt方法釋放鎖定。SpinLock結構還提供了屬性IsHeld和IsHeldByCurrentThread,指定它當前是否是鎖定的。
WaitHandle
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
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);
}
}
Semaphore
信號量非常類似與互斥,其區別是,信號量可以同時由多個線程使用。信號量是一種計數的互斥鎖定。使用信號量,可以定義允許同時訪問受旗語鎖定保護的資源的線程個數。如果需要限制可以訪問可使用資源的線程數,信號量就很有用。例如,如果系統有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");
}
}
}
}
}
Event
事件也是一個系統範圍內的資源同步方法。
- 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();
}
}
}
Barrier
參考
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類。這個類提供了一個鎖定功能,如果沒有寫入器鎖定資源,就允許多個讀取器訪問資源,但只能有一個寫入器鎖定該資源。
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