5 回答

TA貢獻(xiàn)1898條經(jīng)驗(yàn) 獲得超8個(gè)贊
Lock 和 Mutex 哪個(gè)更快?
using System;
using System.Diagnostics;
using System.Threading;
namespace LockingTest
{
? ? class Program
? ? {
? ? ? ? public static object locker = new object();
? ? ? ? public static Mutex mutex = new Mutex();
? ? ? ? public static ManualResetEvent manualResetEvent = new ManualResetEvent(false);
? ? ? ? static void Main(string[] args)
? ? ? ? {
? ? ? ? ? ? Stopwatch sw = new Stopwatch();
? ? ? ? ? ? sw.Restart();
? ? ? ? ? ? for (int i = 0; i < 10000000; i++)
? ? ? ? ? ? {
? ? ? ? ? ? ? ? mutex.WaitOne(); // we are testing mutex time overhead
? ? ? ? ? ? ? ? mutex.ReleaseMutex();
? ? ? ? ? ? }
? ? ? ? ? ? sw.Stop();
? ? ? ? ? ? Console.WriteLine("Mutex :" + "? proccess time token " + sw.Elapsed.ToString() + " miliseconds");
? ? ? ? ? ? Thread.Sleep(1000); // let os to be idle?
? ? ? ? ? ? sw.Restart();
? ? ? ? ? ? for (int i = 0; i < 10000000; i++)
? ? ? ? ? ? {
? ? ? ? ? ? ? ? lock (locker) { } // we are testing lock time overhead
? ? ? ? ? ? }
? ? ? ? ? ? sw.Stop();
? ? ? ? ? ? Console.WriteLine("Lock :" + "? proccess time token " + sw.Elapsed.ToString() + " miliseconds");? ? ? ? ? ?
? ? ? ? ? ? Console.ReadLine();
? ? ? ? }
? ? }
}
如果您將上面的代碼復(fù)制并粘貼到 Visual Stuido 中并運(yùn)行它,您將看到
如您所見,lock
速度比mutex
代碼中的共享資源部分是如何確定的?
為了更好的性能調(diào)整,Visual Studio是否能夠分析共享資源在哪里?
我已將我的 Visual Studio?2010更新到2015,在 Visual Studio 2015 中,當(dāng)您查看每個(gè)方法的頂部時(shí),您將看到參考如下圖所示。
當(dāng)對(duì)方法的引用很高時(shí),內(nèi)存損壞的危險(xiǎn)就會(huì)很高,反之亦然。
如何避免死鎖并在特定超時(shí)后釋放鎖
using System;
? ? using System.Diagnostics;
? ? using System.Threading;
? ? using System.Threading.Tasks;
? ? namespace LockReleaseTest
? ? {
? ? ? ? class Program
? ? ? ? {
? ? ? ? ? ? public static object locker = new object();
? ? ? ? ? ? public static ManualResetEvent mre = new ManualResetEvent(false);
? ? ? ? ? ? public static bool isWorkDone = false;
? ? ? ? ? ? public class StateObject
? ? ? ? ? ? {
? ? ? ? ? ? ? ? public int ThreadNumber;
? ? ? ? ? ? ? ? public string Criticla_Parameter;
? ? ? ? ? ? ? ? public int ItTakes = 1000;
? ? ? ? ? ? }
? ? ? ? ? ? static void Main(string[] args)
? ? ? ? ? ? {
? ? ? ? ? ? ? ? for (int i = 0; i < 5; i++)
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? StateObject state = new StateObject();
? ? ? ? ? ? ? ? ? ? state.ThreadNumber = i;
? ? ? ? ? ? ? ? ? ? state.Criticla_Parameter = "critical " + i.ToString();
? ? ? ? ? ? ? ? ? ? ThreadPool.QueueUserWorkItem(method, state);
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? Thread.Sleep(13000); // wait previous process to be done
? ? ? ? ? ? ? ? Console.WriteLine("In order to test release lock after 2.5 sec press enter");
? ? ? ? ? ? ? ? Console.ReadLine();
? ? ? ? ? ? ? ? for (int i = 0; i < 5; i++)
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? StateObject state = new StateObject();
? ? ? ? ? ? ? ? ? ? state.ThreadNumber = i;
? ? ? ? ? ? ? ? ? ? state.ItTakes = (i + 1) * (1000);
? ? ? ? ? ? ? ? ? ? state.Criticla_Parameter = "critical " + i.ToString();
? ? ? ? ? ? ? ? ? ? ThreadPool.QueueUserWorkItem(method2, state);
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? Console.ReadLine();
? ? ? ? ? ? }
? ? ? ? ? ? public static void method(Object state)
? ? ? ? ? ? {
? ? ? ? ? ? ? ? lock (locker)
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? // critcal section
? ? ? ? ? ? ? ? ? ? string result = ((StateObject)state).Criticla_Parameter;
? ? ? ? ? ? ? ? ? ? int ThreadNumber = ((StateObject)state).ThreadNumber;
? ? ? ? ? ? ? ? ? ? Console.WriteLine("Thread " + ThreadNumber.ToString() + " entered");
? ? ? ? ? ? ? ? ? ? // simultation of process? ? ? ? ? ??
? ? ? ? ? ? ? ? ? ? Thread.Sleep(2000);
? ? ? ? ? ? ? ? ? ? Console.WriteLine("ThreadNumber is " + ThreadNumber + " Result of proccess : " + result);
? ? ? ? ? ? ? ? ? ? // critcal section
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? ? ? public static void method2(Object state)
? ? ? ? ? ? {
? ? ? ? ? ? ? ? if (Monitor.TryEnter(locker, -1))
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? mre.Reset();
? ? ? ? ? ? ? ? ? ? ThreadPool.QueueUserWorkItem(criticalWork, state);
? ? ? ? ? ? ? ? ? ? Thread.Sleep(200);
? ? ? ? ? ? ? ? ? ? ThreadPool.QueueUserWorkItem(LockReleaser, ((StateObject)state).ThreadNumber);
? ? ? ? ? ? ? ? ? ? mre.WaitOne();
? ? ? ? ? ? ? ? ? ? Monitor.Exit(locker);
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? ? ? public static void criticalWork(Object state)
? ? ? ? ? ? {
? ? ? ? ? ? ? ? isWorkDone = false;
? ? ? ? ? ? ? ? string result = ((StateObject)state).Criticla_Parameter;
? ? ? ? ? ? ? ? int ThreadNumber = ((StateObject)state).ThreadNumber;
? ? ? ? ? ? ? ? int HowMuchItTake = ((StateObject)state).ItTakes;
? ? ? ? ? ? ? ? // critcal section
? ? ? ? ? ? ? ? Console.WriteLine("Thread " + ThreadNumber.ToString() + " entered");
? ? ? ? ? ? ? ? // simultation of process? ? ? ? ? ??
? ? ? ? ? ? ? ? Thread.Sleep(HowMuchItTake);
? ? ? ? ? ? ? ? Console.WriteLine("ThreadNumber " + ThreadNumber + " work done. critical parameter is : " + result);
? ? ? ? ? ? ? ? isWorkDone = true;
? ? ? ? ? ? ? ? mre.Set();
? ? ? ? ? ? ? ? // critcal section
? ? ? ? ? ? }
? ? ? ? ? ? public static void LockReleaser(Object ThreadNumber)
? ? ? ? ? ? {
? ? ? ? ? ? ? ? Stopwatch sw = new Stopwatch();
? ? ? ? ? ? ? ? sw.Restart();
? ? ? ? ? ? ? ? do
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? if (isWorkDone) return; // when work is done don't release lock // continue normal
? ? ? ? ? ? ? ? } while (sw.Elapsed.Seconds <= 2.5); // timer in order to release lock
? ? ? ? ? ? ? ? if (!isWorkDone) // more than 2.5 sec time took but work was not done
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? Console.WriteLine("ThreadNumber " + ThreadNumber + " work NOT done. Lock must be released ");
? ? ? ? ? ? ? ? ? ? mre.Set();
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? }
? ? }
如果您將上面的代碼復(fù)制并粘貼到 Visual Studio 中并運(yùn)行它,您將得到如下所示的結(jié)果
正如您所看到的,在第一個(gè)進(jìn)程中,我們不釋放鎖,所有線程都按順序進(jìn)入臨界區(qū),但在第二個(gè)進(jìn)程中,當(dāng)進(jìn)程運(yùn)行很長時(shí)間并且當(dāng)下一個(gè)線程(線程2)釋放鎖時(shí),我們會(huì)釋放鎖進(jìn)入并獲取鎖。因?yàn)椋i必須在父線程中釋放,然后我們用ManualEventRest
信號(hào)通知父線程釋放鎖。我嘗試了其他方法,但它們不起作用并且SynchronizationLockException
發(fā)生了異常。這是我發(fā)現(xiàn)的不引發(fā)異常的最佳方法。

TA貢獻(xiàn)1816條經(jīng)驗(yàn) 獲得超6個(gè)贊
我冒昧地添加第二個(gè)答案,因?yàn)楝F(xiàn)在看來問題的關(guān)鍵部分是如何取消鎖定(即幾秒鐘后釋放它)。
然而,在不取消鎖內(nèi)部正在完成的工作的情況下取消鎖(從鎖的“外部”)是沒有意義的。如果您不取消鎖內(nèi)正在完成的工作,那么它可能會(huì)嘗試?yán)^續(xù)訪問關(guān)鍵資源,從而導(dǎo)致兩個(gè)線程同時(shí)使用該資源。人們應(yīng)該做什么,而不是從外部打破鎖,而應(yīng)該取消正在完成的工作,這將導(dǎo)致該工人退出鎖。
關(guān)于線程和取消的評(píng)論。人們不應(yīng)該中止線程,因?yàn)橥ǔK鼤?huì)使程序(例如該線程持有的資源)處于未定義狀態(tài)。任務(wù)和任務(wù)取消的引入已經(jīng)有好幾年了。任務(wù)本質(zhì)上是一種操作或方法,它與其他任務(wù)一起排隊(duì)等待在從線程池等獲得的線程上執(zhí)行。如今,幾乎所有最近的代碼都應(yīng)該基于任務(wù)并遵循協(xié)作任務(wù)取消方法。以下代碼演示了如何執(zhí)行此操作,包括在線程池上啟動(dòng)任務(wù)。
注意我正在使用我之前的答案中介紹的 MethodLock 類;這只是 SemaphoreSlim 的包裝。
這是一個(gè) Worker 類,它對(duì)關(guān)鍵資源執(zhí)行一些操作(以及一些不使用該資源的操作)。它通過每隔一段時(shí)間測試 CancellationToken 來配合取消。如果請(qǐng)求取消,則工作人員會(huì)通過拋出特殊異常來取消自身。
public class Worker
{
public Worker(int workerId, CancellationToken ct, int howMuchWorkToDo)
{
this.WorkerId = workerId;
this.CancellationToken = ct;
this.ToDo = howMuchWorkToDo;
this.Done = 0;
}
public int WorkerId { get; }
public CancellationToken CancellationToken { get; }
public int ToDo { get; }
public int Done { get; set; }
static MethodLock MethodLock { get; } = new MethodLock();
public async Task DoWorkAwareAsync()
{
this.CancellationToken.ThrowIfCancellationRequested();
this.Done = 0;
while (this.Done < this.ToDo) {
await this.UseCriticalResourceAsync();
await this.OtherWorkAsync();
this.CancellationToken.ThrowIfCancellationRequested();
this.Done += 1;
}
Console.WriteLine($"Worker {this.WorkerId} completed {this.Done} out of {this.ToDo}");
}
private async Task UseCriticalResourceAsync()
{
using (await MethodLock.LockAsync()) {
//Console.WriteLine($"Worker {this.WorkerId} acquired lock on critical resource.");
await Task.Delay(TimeSpan.FromMilliseconds(50));
}
}
private async Task OtherWorkAsync()
{
await Task.Delay(TimeSpan.FromMilliseconds(50));
}
}
現(xiàn)在讓我們看看如何啟動(dòng)一些后臺(tái)工作程序并防止它們運(yùn)行太長時(shí)間,即在幾秒鐘后取消它們。請(qǐng)注意,這是設(shè)置為控制臺(tái)應(yīng)用程序。
任務(wù)被放入線程池中,這意味著系統(tǒng)將在可用線程之間分配任務(wù)。如果需要,系統(tǒng)還可以動(dòng)態(tài)地將任務(wù)重新分配給線程,例如,如果一個(gè)任務(wù)排隊(duì)到一個(gè)繁忙的線程,而另一個(gè)線程空閑。
static void Main(string[] args)
{
Random rand = new Random( DateTime.Now.Millisecond);
Console.WriteLine("---- Cancellation-aware work");
Task[] tasks = new Task[10];
for (int i = 0; i < 10; i++) {
CancellationTokenSource cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromMilliseconds(2000));
int howMuchWork = (rand.Next() % 10) + 1;
Worker w = new Worker(i, cts.Token, howMuchWork);
tasks[i] = Task.Run(
async () => {
try {
await w.DoWorkAwareAsync();
} catch (OperationCanceledException) {
Console.WriteLine($"Canceled worker {w.WorkerId}, work done was {w.Done} out of {w.ToDo}");
}
},
cts.Token
);
}
try {
Task.WaitAll(tasks);
} catch (AggregateException ae) {
foreach (Exception e in ae.InnerExceptions) {
Console.WriteLine($"Exception occurred during work: {e.Message}");
}
}
Console.ReadKey();
}
我想說的是,“cts.Token”作為 Task.Run 的第二個(gè)參數(shù)的存在與強(qiáng)制/硬取消由 Task.Run 方法創(chuàng)建的任務(wù)無關(guān)。Task.Run 對(duì)第二個(gè)參數(shù)所做的就是將其與取消異常中的取消標(biāo)記進(jìn)行比較,如果相同,則 Task.Run 會(huì)將任務(wù)轉(zhuǎn)換為已取消狀態(tài)。
當(dāng)您運(yùn)行此命令時(shí),您將看到類似以下內(nèi)容的內(nèi)容:
---- Cancellation-aware work
Worker 5 completed 1 out of 1
Worker 2 completed 1 out of 1
Worker 8 completed 1 out of 1
Worker 6 completed 3 out of 3
Worker 7 completed 3 out of 3
Canceled worker 3, work done was 4 out of 5
Canceled worker 4, work done was 4 out of 10
Canceled worker 1, work done was 4 out of 8
Canceled worker 9, work done was 4 out of 7
Canceled worker 0, work done was 5 out of 9
同樣,此設(shè)計(jì)假設(shè)工作方法與取消配合。如果您正在使用舊代碼,其中輔助操作不配合偵聽取消請(qǐng)求,則可能需要為該輔助操作創(chuàng)建一個(gè)線程。這需要適當(dāng)?shù)那謇?,此外,它可能?huì)產(chǎn)生性能問題,因?yàn)樗鼤?huì)耗盡線程,而線程是有限的資源。Simon Mourier 在此鏈接討論中的回應(yīng)顯示了如何做到這一點(diǎn):是否可以像中止線程(Thread.Abort 方法)一樣中止任務(wù)?

TA貢獻(xiàn)1775條經(jīng)驗(yàn) 獲得超11個(gè)贊
在具體進(jìn)行鎖定方面,我們來看幾種不同的情況和解決方案。我假設(shè)我們?cè)谶@里使用 C#。此外,我通常會(huì)考慮編寫一個(gè)需要在其內(nèi)部使用鎖定來確保保持一致性的類。
僅螺紋鎖固。在這種情況下,您有多個(gè)線程,只想防止兩個(gè)不同的線程同時(shí)更改內(nèi)存的同一部分(例如雙精度),這會(huì)導(dǎo)致內(nèi)存損壞。您可以只使用 C# 中的“l(fā)ock”語句。然而,在現(xiàn)代編程環(huán)境中,這并不像您想象的那么有用。原因是在“l(fā)ock”語句內(nèi)有多種回調(diào)外部代碼(即類外部的代碼)的方法,并且該外部代碼隨后可以回調(diào)到鎖(可能是異步的)。在這種情況下,第二次遇到“l(fā)ock”語句時(shí),流程很可能會(huì)直接進(jìn)入鎖,無論是否已經(jīng)獲得了鎖。這通常根本不是您想要的。每當(dāng)對(duì)鎖的第二次調(diào)用恰好發(fā)生在與第一次調(diào)用相同的線程上時(shí),就會(huì)發(fā)生這種情況。這種情況很容易發(fā)生,因?yàn)?C# 充滿了任務(wù),這些任務(wù)基本上是可以在單個(gè)線程上執(zhí)行、阻塞其他任務(wù)等的工作單元。
任務(wù)鎖定的目的是保持對(duì)象的狀態(tài)一致性。在這種情況下,類中有一組私有字段,在調(diào)用每個(gè)類方法之前和之后,它們之間必須具有某種不變的關(guān)系。對(duì)這些變量的更改是通過直線代碼完成的,特別是沒有對(duì)類外部代碼的回調(diào),也沒有異步操作。一個(gè)例子是并發(fā)鏈表,其中有一個(gè) _count 字段以及需要與計(jì)數(shù)一致的 _head 和 _tail 指針。在這種情況下,一個(gè)好的方法是以同步方式使用 SemaphoreSlim。我們可以將它包裝在一些方便的類中,如下所示——
public struct ActionOnDispose : IDisposable
{
public ActionOnDispose(Action action) => this.Action = action;
private Action Action { get; }
public void Dispose() => this.Action?.Invoke();
}
public class StateLock
{
private SemaphoreSlim Semaphore { get; } = new SemaphoreSlim(1, 1);
public bool IsLocked => this.Semaphore.CurrentCount == 0;
public ActionOnDispose Lock()
{
this.Semaphore.Wait();
return new ActionOnDispose(() => this.Semaphore.Release());
}
}
StateLock 類的要點(diǎn)是使用信號(hào)量的唯一方法是通過 Wait,而不是通過 WaitAsync。稍后會(huì)詳細(xì)介紹這一點(diǎn)。注釋:ActionOnDispose 的目的是啟用諸如“using (stateLock.Lock()) { … }”之類的語句。
任務(wù)級(jí)鎖定的目的是防止重新進(jìn)入方法,其中在鎖內(nèi)方法可能會(huì)調(diào)用用戶回調(diào)或類外部的其他代碼。這將包括鎖內(nèi)存在異步操作的所有情況,例如“等待”——當(dāng)您等待時(shí),任何其他任務(wù)都可能運(yùn)行并回調(diào)到您的方法中。在這種情況下,一個(gè)好的方法是再次使用 SemaphoreSlim,但使用異步簽名。下面的類提供了一些附加功能,本質(zhì)上是對(duì) SemaphoreSlim(1,1).WaitAsync() 的調(diào)用。您可以在“using (await methodLock.LockAsync()) { … }”之類的代碼構(gòu)造中使用它。注釋:輔助結(jié)構(gòu)的目的是防止意外省略 using 語句中的“await”。
public class MethodLock
{
private SemaphoreSlim Semaphore { get; } = new SemaphoreSlim(1, 1);
public bool IsLocked => this.CurrentCount == 0;
private async Task<ActionOnDispose> RequestLockAsync()
{
await this.Semaphore.WaitAsync().ConfigureAwait(false);
return new ActionOnDispose( () => this.Semaphore.Release());
}
public TaskReturningActionOnDispose LockAsync()
{
return new TaskReturningActionOnDispose(this.RequestLockAsync());
}
}
public struct TaskReturningActionOnDispose
{
private Task<ActionOnDispose> TaskResultingInActionOnDispose { get; }
public TaskReturningActionOnDispose(Task<ActionOnDispose> task)
{
if (task == null) { throw new ArgumentNullException("task"); }
this.TaskResultingInActionOnDispose = task;
}
// Here is the key method, that makes it awaitable.
public TaskAwaiter<ActionOnDispose> GetAwaiter()
{
return this.TaskResultingInActionOnDispose.GetAwaiter();
}
}
您不想做的就是在同一個(gè) SemaphoreSlim 上自由地將 LockAsync() 和 Lock() 混合在一起。經(jīng)驗(yàn)表明,這很快就會(huì)導(dǎo)致許多難以識(shí)別的死鎖。另一方面,如果你堅(jiān)持上面的兩類,你就不會(huì)遇到這些問題。仍然可能出現(xiàn)死鎖,例如,如果在 Lock() 中調(diào)用另一個(gè)也執(zhí)行 Lock() 的類方法,或者在方法中執(zhí)行 LockAsync(),然后回調(diào)的用戶代碼嘗試執(zhí)行重新輸入同樣的方法。但防止這些重入情況正是鎖的重點(diǎn)——這些情況下的死鎖是“正?!卞e(cuò)誤,代表設(shè)計(jì)中的邏輯錯(cuò)誤,并且處理起來相當(dāng)簡單。對(duì)此有一個(gè)提示,如果您想輕松檢測此類死鎖,您可以做的是,在實(shí)際執(zhí)行 Wait() 或 WaitAsync() 之前,您可以首先執(zhí)行帶有超時(shí)的初步 Wait/WaitAsync,如果發(fā)生超時(shí),則打印一條消息,說明可能存在死鎖。顯然,您可以在#if DEBUG / #endif 中執(zhí)行此操作。
另一種典型的鎖定情況是當(dāng)您希望某些任務(wù)等待直到另一個(gè)任務(wù)將條件設(shè)置為 true 時(shí)。例如,您可能想要等到應(yīng)用程序初始化。為此,請(qǐng)使用 TaskCompletionSource 創(chuàng)建一個(gè)等待標(biāo)志,如以下類所示。您也可以使用 ManualResetEventSlim,但如果您這樣做,則需要進(jìn)行處置,這一點(diǎn)也不方便。
public class Null { private Null() {} } // a reference type whose only possible value is null.
public class WaitFlag
{
public WaitFlag()
{
this._taskCompletionSource = new TaskCompletionSource<Null>(TaskCreationOptions.RunContinuationsAsynchronously);
}
public WaitFlag( bool value): this()
{
this.Value = value;
}
private volatile TaskCompletionSource<Null> _taskCompletionSource;
public static implicit operator bool(WaitFlag waitFlag) => waitFlag.Value;
public override string ToString() => ((bool)this).ToString();
public async Task WaitAsync()
{
Task waitTask = this._taskCompletionSource.Task;
await waitTask;
}
public void Set() => this.Value = true;
public void Reset() => this.Value = false;
public bool Value {
get {
return this._taskCompletionSource.Task.IsCompleted;
}
set {
if (value) { // set
this._taskCompletionSource.TrySetResult(null);
} else { // reset
var tcs = this._taskCompletionSource;
if (tcs.Task.IsCompleted) {
bool didReset = (tcs == Interlocked.CompareExchange(ref this._taskCompletionSource, new TaskCompletionSource<Null>(TaskCreationOptions.RunContinuationsAsynchronously), tcs));
Debug.Assert(didReset);
}
}
}
}
}
最后,當(dāng)您想要自動(dòng)測試并設(shè)置標(biāo)志時(shí)。當(dāng)您只想執(zhí)行尚未執(zhí)行的操作時(shí),這非常有用。您可以使用 StateLock 來執(zhí)行此操作。然而,針對(duì)這種特定情況的輕量級(jí)解決方案是使用 Interlocked 類。就我個(gè)人而言,我發(fā)現(xiàn)互鎖代碼讀起來很混亂,因?yàn)槲矣肋h(yuǎn)記不起正在測試哪個(gè)參數(shù)以及正在設(shè)置哪個(gè)參數(shù)。使其成為 TestAndSet 操作:
public class InterlockedBoolean
{
private int _flag; // 0 means false, 1 means true
// Sets the flag if it was not already set, and returns the value that the flag had before the operation.
public bool TestAndSet()
{
int ifEqualTo = 0;
int thenAssignValue = 1;
int oldValue = Interlocked.CompareExchange(ref this._flag, thenAssignValue, ifEqualTo);
return oldValue == 1;
}
public void Unset()
{
int ifEqualTo = 1;
int thenAssignValue = 0;
int oldValue = Interlocked.CompareExchange(ref this._flag, thenAssignValue, ifEqualTo);
if (oldValue != 1) { throw new InvalidOperationException("Flag was already unset."); }
}
}
我想說的是,上面的代碼都不是出色的原創(chuàng)。所有這些都有很多前因,您可以通過在互聯(lián)網(wǎng)上進(jìn)行足夠的搜索來找到。這方面的著名作者包括 Toub、Hanselman、Cleary 等。WaitFlag 中的“互鎖”部分基于 Toub 的一篇文章,我自己覺得這有點(diǎn)令人費(fèi)解。
編輯:我上面沒有展示的一件事是,例如,當(dāng)您絕對(duì)必須同步鎖定但類設(shè)計(jì)需要 MethodLock 而不是 StateLock 時(shí)該怎么做。在這種情況下,您可以做的是向 MethodLock 添加一個(gè)方法 LockOrThrow ,該方法將測試鎖,如果在(非常)短的超時(shí)后無法獲得鎖,則拋出異常。這允許您同步鎖定,同時(shí)防止自由混合 Lock 和 LockAsync 時(shí)可能出現(xiàn)的各種問題。當(dāng)然,由您來確保不會(huì)發(fā)生投擲。
編輯:這是為了解決原始帖子中的具體概念和問題。
(a) 如何保護(hù)方法中的關(guān)鍵部分。將鎖放入“using”語句中(如下所示),您可以讓多個(gè)任務(wù)調(diào)用該方法(或類中的多個(gè)方法),而無需同時(shí)執(zhí)行任何兩個(gè)臨界區(qū)。
public class ThreadSafeClass {
private StateLock StateLock { get; } = new StateLock();
public void FirstMethod(string param)
{
using (this.StateLock.Lock()) {
** critical section **
// There are a lot of methods calls but not to other locked methods
// Switch cases, if conditions, dictionary use, etc -- no problem
// But NOT: await SomethingAsync();
// and NOT: callbackIntoUserCode();
** critical section **
}
}
public void SecondMethod()
{
using (this.StateLock.Lock()) {
** another, possibly different, critical section **
}
}
}
public class ThreadSafeAsyncClass {
private MethodLock MethodLock { get; } = new MethodLock();
public async Task FirstMethodAsync(string param)
{
using (await this.MethodLock.LockAsync()) {
** critical section **
await SomethingAsync(); // OK
callbackIntoUserCode(); // OK
}
}
public async Task SecondMethodAsync()
{
using (await this.MethodLock.LockAsync()) {
** another, possibly different, critical section using async **
}
}
}
(b) 鑒于 Delegate 是線程安全類,委托可以提供幫助嗎?沒有。當(dāng)我們說一個(gè)類是線程安全的時(shí),意味著它將成功執(zhí)行來自多個(gè)線程的多個(gè)調(diào)用(通常它們實(shí)際上指的是任務(wù))。對(duì)于 Delegate 來說也是如此;由于委托中的數(shù)據(jù)均不可更改,因此該數(shù)據(jù)不可能被損壞。委托所做的就是調(diào)用您指定的方法(或代碼塊)。如果委托正在調(diào)用您的方法,并且在執(zhí)行此操作時(shí)另一個(gè)線程使用同一委托也調(diào)用您的方法,則委托將成功為兩個(gè)線程調(diào)用您的方法。但是,委托不會(huì)執(zhí)行任何操作來確保您的方法是線程安全的。當(dāng)兩個(gè)方法調(diào)用執(zhí)行時(shí),它們可能會(huì)互相干擾。因此,盡管 Delegate 是調(diào)用方法的線程安全方式,但它并不能保護(hù)該方法??傊?,委托幾乎不會(huì)對(duì)線程安全產(chǎn)生影響。
(c) 鎖的示意圖和正確使用方法。在圖中,“線程安全部分”的標(biāo)簽不正確。線程安全部分是鎖內(nèi)的部分(在上例中的“using”塊內(nèi)),圖中顯示“Call Method”。該圖的另一個(gè)問題是,它似乎顯示在左側(cè)的調(diào)用方法周圍以及右側(cè)的方法內(nèi)都使用了相同的鎖。這樣做的問題是,如果在調(diào)用該方法之前加鎖,那么當(dāng)你進(jìn)入該方法并再次嘗試加鎖時(shí),將無法第二次獲得鎖。
(d) Lock 或 Mutex 哪個(gè)更快?一般來說,速度問題很困難,因?yàn)樗Q于很多因素。但從廣義上講,在單個(gè)進(jìn)程內(nèi)有效的鎖(例如 SemaphoreSlim、Interlocked 和“l(fā)ock”關(guān)鍵字)將比跨進(jìn)程有效的鎖(例如 Semaphore 和 Mutex)具有更快的性能。聯(lián)鎖方法可能是最快的。
(e) 識(shí)別共享資源以及 Visual Studio 是否可以自動(dòng)識(shí)別它們。這幾乎是設(shè)計(jì)優(yōu)秀軟件所面臨的挑戰(zhàn)。但是,如果您采用將資源包裝在線程安全類中的方法,那么除了通過類之外,任何代碼都不會(huì)有訪問這些資源的風(fēng)險(xiǎn)。這樣,您就不必搜索整個(gè)代碼庫來查看資源的訪問位置并使用鎖保護(hù)這些訪問。
(f) 如何在 2.5 秒后釋放鎖并將其他訪問該鎖的請(qǐng)求排隊(duì)。我可以想出幾種方法來解釋這個(gè)問題。如果您想做的只是讓其他請(qǐng)求等待直到鎖被釋放,并且您想要在鎖中做一些需要 2.5 秒的事情,那么您不必做任何特殊的事情。例如,在上面的 ThreadSafeAsyncClass 中,您可以簡單地將“await Task.Delay( Timespan.FromSeconds(2.5))”放入 FirstMethodAsync 中的“using”塊內(nèi)。當(dāng)一個(gè)任務(wù)正在執(zhí)行“await FirstMethodAsync("")”時(shí),其他任務(wù)將等待第一個(gè)任務(wù)完成,這將需要大約 2.5 秒。另一方面,如果您想要做的是擁有一個(gè)生產(chǎn)者-消費(fèi)者隊(duì)列,那么您應(yīng)該做的是使用 StateLock 中描述的方法;生產(chǎn)者應(yīng)該在將某些內(nèi)容放入隊(duì)列時(shí)短暫地獲取鎖,而消費(fèi)者也應(yīng)該在從隊(duì)列另一端取出某些內(nèi)容時(shí)短暫地獲取鎖。

TA貢獻(xiàn)1812條經(jīng)驗(yàn) 獲得超5個(gè)贊
人們提出了很多問題,但我會(huì)盡力解決所有問題。
當(dāng)發(fā)生數(shù)千個(gè)并發(fā)調(diào)用時(shí),我們?nèi)绾问蛊渚€程安全?
要使方法完全線程安全,您可以將其編寫為沒有副作用。沒有副作用的方法不會(huì)訪問任何共享資源。
代表們可以幫忙嗎?這是否意味著委托使我的代碼線程安全?委托何時(shí)發(fā)揮線程安全作用?
C# 中的委托類似于 C++ 中的函數(shù)指針。它們?cè)试S您將方法分配給變量,然后通過該變量調(diào)用該方法來調(diào)用該方法。使用委托獲得的唯一“線程安全”保證是,調(diào)用委托時(shí)它將成功調(diào)用分配給它的函數(shù)。被調(diào)用的函數(shù)的執(zhí)行方式與您在同一位置對(duì)它的調(diào)用進(jìn)行硬編碼時(shí)完全相同。
上圖中,Locker的正確使用是什么?方法內(nèi)部還是方法外部?為什么?
我認(rèn)為這兩種選擇對(duì)于放置鎖來說都不太理想。同步對(duì)象的目的是防止同時(shí)訪問資源。每個(gè)共享資源都應(yīng)該有自己的鎖,使用這些鎖的最佳位置是在實(shí)際使用其關(guān)聯(lián)資源的幾個(gè)關(guān)鍵行周圍。如果您總是將鎖放在整個(gè)函數(shù)體周圍,那么您可能會(huì)阻塞其他線程超過必要的時(shí)間,從而降低整體性能。
Lock 和 Mutex 哪個(gè)更快?
它們有不同的用途。
該lock
語句是 C# 語言的一部分。使用此關(guān)鍵字可以清理您的代碼并清楚地概述關(guān)鍵部分。
另一方面,互斥鎖是一個(gè)可以在進(jìn)程之間共享的對(duì)象,因此它旨在用于 IPC。如果您不將其用于 IPC,我認(rèn)為沒有任何理由放棄該lock
語法。Mutex
代碼中的共享資源部分是如何確定的?
我將舉一個(gè)類比來幫助您識(shí)別共享資源。
想象一下您的線程是建筑工地上的工人。該站點(diǎn)有一個(gè)便攜式廁所和一些電動(dòng)工具。每個(gè)工人都有不同的工作要做,因此他們拿起各自的工具(不共享)并開始工作。在某些時(shí)候,這些工人中的每一個(gè)都必須使用廁所。廁所有鎖,保證一次只有一名工人使用。如果當(dāng)其他工人需要廁所時(shí)廁所被鎖了,那么他們就會(huì)排隊(duì)等待解鎖。
在這一類比中,強(qiáng)大的工具可能是私有類變量或只有一個(gè)線程需要訪問的對(duì)象。而廁所是一個(gè)對(duì)象,在某一時(shí)刻多個(gè)線程必須訪問該對(duì)象。這使其成為共享資源。
Visual Studio 是否能夠分析資源共享的位置以及需要使其線程安全的位置?
在調(diào)試器中運(yùn)行代碼,看看出現(xiàn)了什么問題!調(diào)試器將幫助您識(shí)別死鎖等線程問題,并且在暫停時(shí)您可以看到每個(gè)線程當(dāng)前正在執(zhí)行的方法。如果您看到兩個(gè)線程使用同一個(gè)變量,那么它就是共享資源。
如何讓獲得鎖的線程在2.5秒后釋放鎖并將所有其他需要鎖的線程排隊(duì)?
這個(gè)問題確實(shí)應(yīng)該是它自己的帖子。
如果一個(gè)線程鎖定了某個(gè)東西,它就負(fù)責(zé)解鎖它。如果鎖定部分花費(fèi)的時(shí)間太長,那么您的設(shè)計(jì)可能存在問題。實(shí)現(xiàn)計(jì)時(shí)器來“切斷”具有鎖的線程是一種危險(xiǎn)的設(shè)計(jì)。相反,您可以在線程方法中放置“檢查點(diǎn)”,使用在方法開頭啟動(dòng)的計(jì)時(shí)器來檢查它是否執(zhí)行了太長時(shí)間。如果需要退出,則應(yīng)釋放鎖并提前退出該方法,以便不再訪問共享資源。
使用該lock
語法會(huì)自動(dòng)導(dǎo)致其他線程等待鎖釋放。如果多個(gè)線程需要相同的鎖,則無法保證它們接收鎖的順序。

TA貢獻(xiàn)1777條經(jīng)驗(yàn) 獲得超3個(gè)贊
這是一個(gè)例子??梢訽sharedString由兩個(gè)函數(shù)訪問,MethodAdd并且MethodDelete可以從不同的線程調(diào)用。為了確保對(duì) 的訪問_sharedString是序列化的,即一次一個(gè)線程,我們通常創(chuàng)建一個(gè)鎖對(duì)象,然后使用 C# 關(guān)鍵字lock來獲得對(duì)共享資源的獨(dú)占訪問,在本例中是_sharedString。
private static object _lock = new object();
private string _sharedString = "";
public void MethodAdd(string s)
{
lock(_lock)
{
// Exclusive access to _sharedString.
_sharedString = _sharedString + s;
}
}
public void MethodDelete(int n)
{
lock (_lock)
{
// Exclusive access to _sharedString.
_sharedString = _sharedString.Substring(0, n);
}
}
您在問題中提到“線程安全”我的意思是我想要多個(gè)并發(fā)操作 - 其中沒有一個(gè)操作會(huì)相互阻塞,但這是不可能實(shí)現(xiàn)的。為了實(shí)現(xiàn)線程安全,總會(huì)有一定量的阻塞。如果您的服務(wù)器變得太慢lock(您在問題中沒有提到,但僅在評(píng)論中提到),那么您應(yīng)該修改您的設(shè)計(jì);您的共享資源是瓶頸。
- 5 回答
- 0 關(guān)注
- 251 瀏覽
添加回答
舉報(bào)