亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

為了賬號安全,請及時綁定郵箱和手機立即綁定
已解決430363個問題,去搜搜看,總會有你想問的

如何使用鎖和委托來同步方法訪問(線程安全)

如何使用鎖和委托來同步方法訪問(線程安全)

C#
楊魅力 2023-07-09 15:27:29
假設我們有這樣的方法public static void method(string param){? ? ** critical section **? ? // There are a lot of methods calls?? ? // switch cases?? ? // if conditions?? ? // read and write in dictionary?? ? // new class initiations? ? ** critical section **??}thread-safe當發生數千個并發調用時我們如何做到這一點?可以delegate幫忙嗎?修改事件不是線程安全的,但調用委托是線程安全的。由于 Delegate 是不可變類型,因此它是線程安全的。這是否意味著delegate我的代碼是這樣的thread-safe?如果delegate不授予thread-safe并發調用。你能解釋一下為什么嗎?如果Lock受資助者thread-safe是這樣的話:如何避免Deadlock并在特定超時后釋放鎖?MutexLock在某些方面類似于。是Lock或者Mutex更快?為了更好的性能調整,DoseVisual Studio有能力分析共享資源在哪里嗎?
查看完整描述

5 回答

?
汪汪一只貓

TA貢獻1898條經驗 獲得超8個贊

Lock 和 Mutex 哪個更快?


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();

? ? ? ? }

? ? }

}

如果您將上面的代碼復制并粘貼到 Visual Stuido 中并運行它,您將看到

http://img4.sycdn.imooc.com/64aa6bad000138a704810048.jpg

如您所見,lock速度mutex

代碼中的共享資源部分是如何確定的?

為了更好的性能調整,Visual Studio是否能夠分析共享資源在哪里?

我已將我的 Visual Studio?2010更新到2015,在 Visual Studio 2015 中,當您查看每個方法的頂部時,您將看到參考如下圖所示。

http://img1.sycdn.imooc.com/64aa6bbc000126ba06300095.jpg

當對方法的引用很高時,內存損壞的危險就會很高,反之亦然。


如何避免死鎖并在特定超時后釋放鎖


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();

? ? ? ? ? ? ? ? }

? ? ? ? ? ? }

? ? ? ? }

? ? }

如果您將上面的代碼復制并粘貼到 Visual Studio 中并運行它,您將得到如下所示的結果

http://img3.sycdn.imooc.com/64aa6bd300018ff011870439.jpg

正如您所看到的,在第一個進程中,我們不釋放鎖,所有線程都按順序進入臨界區,但在第二個進程中,當進程運行很長時間并且當下一個線程(線程2)釋放鎖時,我們會釋放鎖進入并獲取鎖。因為,鎖必須在父線程中釋放,然后我們用ManualEventRest信號通知父線程釋放鎖。我嘗試了其他方法,但它們不起作用并且SynchronizationLockException發生了異常。這是我發現的不引發異常的最佳方法。


查看完整回答
反對 回復 2023-07-09
?
瀟湘沐

TA貢獻1816條經驗 獲得超6個贊

我冒昧地添加第二個答案,因為現在看來問題的關鍵部分是如何取消鎖定(即幾秒鐘后釋放它)。


然而,在不取消鎖內部正在完成的工作的情況下取消鎖(從鎖的“外部”)是沒有意義的。如果您不取消鎖內正在完成的工作,那么它可能會嘗試繼續訪問關鍵資源,從而導致兩個線程同時使用該資源。人們應該做什么,而不是從外部打破鎖,而應該取消正在完成的工作,這將導致該工人退出鎖。


關于線程和取消的評論。人們不應該中止線程,因為通常它會使程序(例如該線程持有的資源)處于未定義狀態。任務和任務取消的引入已經有好幾年了。任務本質上是一種操作或方法,它與其他任務一起排隊等待在從線程池等獲得的線程上執行。如今,幾乎所有最近的代碼都應該基于任務并遵循協作任務取消方法。以下代碼演示了如何執行此操作,包括在線程池上啟動任務。


注意我正在使用我之前的答案中介紹的 MethodLock 類;這只是 SemaphoreSlim 的包裝。


這是一個 Worker 類,它對關鍵資源執行一些操作(以及一些不使用該資源的操作)。它通過每隔一段時間測試 CancellationToken 來配合取消。如果請求取消,則工作人員會通過拋出特殊異常來取消自身。


        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));

            }

        }

現在讓我們看看如何啟動一些后臺工作程序并防止它們運行太長時間,即在幾秒鐘后取消它們。請注意,這是設置為控制臺應用程序。


任務被放入線程池中,這意味著系統將在可用線程之間分配任務。如果需要,系統還可以動態地將任務重新分配給線程,例如,如果一個任務排隊到一個繁忙的線程,而另一個線程空閑。


        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 的第二個參數的存在與強制/硬取消由 Task.Run 方法創建的任務無關。Task.Run 對第二個參數所做的就是將其與取消異常中的取消標記進行比較,如果相同,則 Task.Run 會將任務轉換為已取消狀態。


當您運行此命令時,您將看到類似以下內容的內容:


    ---- 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

同樣,此設計假設工作方法與取消配合。如果您正在使用舊代碼,其中輔助操作不配合偵聽取消請求,則可能需要為該輔助操作創建一個線程。這需要適當的清理,此外,它可能會產生性能問題,因為它會耗盡線程,而線程是有限的資源。Simon Mourier 在此鏈接討論中的回應顯示了如何做到這一點:是否可以像中止線程(Thread.Abort 方法)一樣中止任務?


查看完整回答
反對 回復 2023-07-09
?
繁星淼淼

TA貢獻1775條經驗 獲得超11個贊

在具體進行鎖定方面,我們來看幾種不同的情況和解決方案。我假設我們在這里使用 C#。此外,我通常會考慮編寫一個需要在其內部使用鎖定來確保保持一致性的類。


僅螺紋鎖固。在這種情況下,您有多個線程,只想防止兩個不同的線程同時更改內存的同一部分(例如雙精度),這會導致內存損壞。您可以只使用 C# 中的“lock”語句。然而,在現代編程環境中,這并不像您想象的那么有用。原因是在“lock”語句內有多種回調外部代碼(即類外部的代碼)的方法,并且該外部代碼隨后可以回調到鎖(可能是異步的)。在這種情況下,第二次遇到“lock”語句時,流程很可能會直接進入鎖,無論是否已經獲得了鎖。這通常根本不是您想要的。每當對鎖的第二次調用恰好發生在與第一次調用相同的線程上時,就會發生這種情況。這種情況很容易發生,因為 C# 充滿了任務,這些任務基本上是可以在單個線程上執行、阻塞其他任務等的工作單元。


任務鎖定的目的是保持對象的狀態一致性。在這種情況下,類中有一組私有字段,在調用每個類方法之前和之后,它們之間必須具有某種不變的關系。對這些變量的更改是通過直線代碼完成的,特別是沒有對類外部代碼的回調,也沒有異步操作。一個例子是并發鏈表,其中有一個 _count 字段以及需要與計數一致的 _head 和 _tail 指針。在這種情況下,一個好的方法是以同步方式使用 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 類的要點是使用信號量的唯一方法是通過 Wait,而不是通過 WaitAsync。稍后會詳細介紹這一點。注釋:ActionOnDispose 的目的是啟用諸如“using (stateLock.Lock()) { … }”之類的語句。


任務級鎖定的目的是防止重新進入方法,其中在鎖內方法可能會調用用戶回調或類外部的其他代碼。這將包括鎖內存在異步操作的所有情況,例如“等待”——當您等待時,任何其他任務都可能運行并回調到您的方法中。在這種情況下,一個好的方法是再次使用 SemaphoreSlim,但使用異步簽名。下面的類提供了一些附加功能,本質上是對 SemaphoreSlim(1,1).WaitAsync() 的調用。您可以在“using (await methodLock.LockAsync()) { … }”之類的代碼構造中使用它。注釋:輔助結構的目的是防止意外省略 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();

        }

    }

您不想做的就是在同一個 SemaphoreSlim 上自由地將 LockAsync() 和 Lock() 混合在一起。經驗表明,這很快就會導致許多難以識別的死鎖。另一方面,如果你堅持上面的兩類,你就不會遇到這些問題。仍然可能出現死鎖,例如,如果在 Lock() 中調用另一個也執行 Lock() 的類方法,或者在方法中執行 LockAsync(),然后回調的用戶代碼嘗試執行重新輸入同樣的方法。但防止這些重入情況正是鎖的重點——這些情況下的死鎖是“正常”錯誤,代表設計中的邏輯錯誤,并且處理起來相當簡單。對此有一個提示,如果您想輕松檢測此類死鎖,您可以做的是,在實際執行 Wait() 或 WaitAsync() 之前,您可以首先執行帶有超時的初步 Wait/WaitAsync,如果發生超時,則打印一條消息,說明可能存在死鎖。顯然,您可以在#if DEBUG / #endif 中執行此操作。


另一種典型的鎖定情況是當您希望某些任務等待直到另一個任務將條件設置為 true 時。例如,您可能想要等到應用程序初始化。為此,請使用 TaskCompletionSource 創建一個等待標志,如以下類所示。您也可以使用 ManualResetEventSlim,但如果您這樣做,則需要進行處置,這一點也不方便。


    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);

                    }

                }

            }

        }

    }

最后,當您想要自動測試并設置標志時。當您只想執行尚未執行的操作時,這非常有用。您可以使用 StateLock 來執行此操作。然而,針對這種特定情況的輕量級解決方案是使用 Interlocked 類。就我個人而言,我發現互鎖代碼讀起來很混亂,因為我永遠記不起正在測試哪個參數以及正在設置哪個參數。使其成為 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."); }

        }

    }


我想說的是,上面的代碼都不是出色的原創。所有這些都有很多前因,您可以通過在互聯網上進行足夠的搜索來找到。這方面的著名作者包括 Toub、Hanselman、Cleary 等。WaitFlag 中的“互鎖”部分基于 Toub 的一篇文章,我自己覺得這有點令人費解。


編輯:我上面沒有展示的一件事是,例如,當您絕對必須同步鎖定但類設計需要 MethodLock 而不是 StateLock 時該怎么做。在這種情況下,您可以做的是向 MethodLock 添加一個方法 LockOrThrow ,該方法將測試鎖,如果在(非常)短的超時后無法獲得鎖,則拋出異常。這允許您同步鎖定,同時防止自由混合 Lock 和 LockAsync 時可能出現的各種問題。當然,由您來確保不會發生投擲。


編輯:這是為了解決原始帖子中的具體概念和問題。


(a) 如何保護方法中的關鍵部分。將鎖放入“using”語句中(如下所示),您可以讓多個任務調用該方法(或類中的多個方法),而無需同時執行任何兩個臨界區。


    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 是線程安全類,委托可以提供幫助嗎?沒有。當我們說一個類是線程安全的時,意味著它將成功執行來自多個線程的多個調用(通常它們實際上指的是任務)。對于 Delegate 來說也是如此;由于委托中的數據均不可更改,因此該數據不可能被損壞。委托所做的就是調用您指定的方法(或代碼塊)。如果委托正在調用您的方法,并且在執行此操作時另一個線程使用同一委托也調用您的方法,則委托將成功為兩個線程調用您的方法。但是,委托不會執行任何操作來確保您的方法是線程安全的。當兩個方法調用執行時,它們可能會互相干擾。因此,盡管 Delegate 是調用方法的線程安全方式,但它并不能保護該方法??傊袔缀醪粫€程安全產生影響。


(c) 鎖的示意圖和正確使用方法。在圖中,“線程安全部分”的標簽不正確。線程安全部分是鎖內的部分(在上例中的“using”塊內),圖中顯示“Call Method”。該圖的另一個問題是,它似乎顯示在左側的調用方法周圍以及右側的方法內都使用了相同的鎖。這樣做的問題是,如果在調用該方法之前加鎖,那么當你進入該方法并再次嘗試加鎖時,將無法第二次獲得鎖。


(d) Lock 或 Mutex 哪個更快?一般來說,速度問題很困難,因為它取決于很多因素。但從廣義上講,在單個進程內有效的鎖(例如 SemaphoreSlim、Interlocked 和“lock”關鍵字)將比跨進程有效的鎖(例如 Semaphore 和 Mutex)具有更快的性能。聯鎖方法可能是最快的。


(e) 識別共享資源以及 Visual Studio 是否可以自動識別它們。這幾乎是設計優秀軟件所面臨的挑戰。但是,如果您采用將資源包裝在線程安全類中的方法,那么除了通過類之外,任何代碼都不會有訪問這些資源的風險。這樣,您就不必搜索整個代碼庫來查看資源的訪問位置并使用鎖保護這些訪問。


(f) 如何在 2.5 秒后釋放鎖并將其他訪問該鎖的請求排隊。我可以想出幾種方法來解釋這個問題。如果您想做的只是讓其他請求等待直到鎖被釋放,并且您想要在鎖中做一些需要 2.5 秒的事情,那么您不必做任何特殊的事情。例如,在上面的 ThreadSafeAsyncClass 中,您可以簡單地將“await Task.Delay( Timespan.FromSeconds(2.5))”放入 FirstMethodAsync 中的“using”塊內。當一個任務正在執行“await FirstMethodAsync("")”時,其他任務將等待第一個任務完成,這將需要大約 2.5 秒。另一方面,如果您想要做的是擁有一個生產者-消費者隊列,那么您應該做的是使用 StateLock 中描述的方法;生產者應該在將某些內容放入隊列時短暫地獲取鎖,而消費者也應該在從隊列另一端取出某些內容時短暫地獲取鎖。


查看完整回答
反對 回復 2023-07-09
?
ABOUTYOU

TA貢獻1812條經驗 獲得超5個贊

人們提出了很多問題,但我會盡力解決所有問題。

當發生數千個并發調用時,我們如何使其線程安全?

要使方法完全線程安全,您可以將其編寫為沒有副作用。沒有副作用的方法不會訪問任何共享資源。


代表們可以幫忙嗎?這是否意味著委托使我的代碼線程安全?委托何時發揮線程安全作用?

C# 中的委托類似于 C++ 中的函數指針。它們允許您將方法分配給變量,然后通過該變量調用該方法來調用該方法。使用委托獲得的唯一“線程安全”保證是,調用委托時它將成功調用分配給它的函數。被調用的函數的執行方式與您在同一位置對它的調用進行硬編碼時完全相同。


上圖中,Locker的正確使用是什么?方法內部還是方法外部?為什么?

我認為這兩種選擇對于放置鎖來說都不太理想。同步對象的目的是防止同時訪問資源。每個共享資源都應該有自己的鎖,使用這些鎖的最佳位置是在實際使用其關聯資源的幾個關鍵行周圍。如果您總是將鎖放在整個函數體周圍,那么您可能會阻塞其他線程超過必要的時間,從而降低整體性能。


Lock 和 Mutex 哪個更快?

它們有不同的用途。

lock語句是 C# 語言的一部分。使用此關鍵字可以清理您的代碼并清楚地概述關鍵部分。

另一方面,互斥鎖是一個可以在進程之間共享的對象,因此它旨在用于 IPC。如果您將其用于 IPC,我認為沒有任何理由放棄該lock語法。Mutex


代碼中的共享資源部分是如何確定的?

我將舉一個類比來幫助您識別共享資源。

想象一下您的線程是建筑工地上的工人。該站點有一個便攜式廁所和一些電動工具。每個工人都有不同的工作要做,因此他們拿起各自的工具(不共享)并開始工作。在某些時候,這些工人中的每一個都必須使用廁所。廁所有鎖,保證一次只有一名工人使用。如果當其他工人需要廁所時廁所被鎖了,那么他們就會排隊等待解鎖。

在這一類比中,強大的工具可能是私有類變量或只有一個線程需要訪問的對象。而廁所是一個對象,在某一時刻多個線程必須訪問該對象。這使其成為共享資源。

Visual Studio 是否能夠分析資源共享的位置以及需要使其線程安全的位置?

在調試器中運行代碼,看看出現了什么問題!調試器將幫助您識別死鎖等線程問題,并且在暫停時您可以看到每個線程當前正在執行的方法。如果您看到兩個線程使用同一個變量,那么它就是共享資源。


如何讓獲得鎖的線程在2.5秒后釋放鎖并將所有其他需要鎖的線程排隊?

這個問題確實應該是它自己的帖子。

如果一個線程鎖定了某個東西,它就負責解鎖它。如果鎖定部分花費的時間太長,那么您的設計可能存在問題。實現計時器來“切斷”具有鎖的線程是一種危險的設計。相反,您可以在線程方法中放置“檢查點”,使用在方法開頭啟動的計時器來檢查它是否執行了太長時間。如果需要退出,則應釋放鎖并提前退出該方法,以便不再訪問共享資源。

使用該lock語法會自動導致其他線程等待鎖釋放。如果多個線程需要相同的鎖,則無法保證它們接收鎖的順序。


查看完整回答
反對 回復 2023-07-09
?
慕森王

TA貢獻1777條經驗 獲得超3個贊

這是一個例子??梢訽sharedString由兩個函數訪問,MethodAdd并且MethodDelete可以從不同的線程調用。為了確保對 的訪問_sharedString是序列化的,即一次一個線程,我們通常創建一個鎖對象,然后使用 C# 關鍵字lock來獲得對共享資源的獨占訪問,在本例中是_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);

    }

}

您在問題中提到“線程安全”我的意思是我想要多個并發操作 - 其中沒有一個操作會相互阻塞,但這是不可能實現的。為了實現線程安全,總會有一定量的阻塞。如果您的服務器變得太慢lock(您在問題中沒有提到,但僅在評論中提到),那么您應該修改您的設計;您的共享資源是瓶頸。


查看完整回答
反對 回復 2023-07-09
  • 5 回答
  • 0 關注
  • 236 瀏覽

添加回答

舉報

0/150
提交
取消
微信客服

購課補貼
聯系客服咨詢優惠詳情

幫助反饋 APP下載

慕課網APP
您的移動學習伙伴

公眾號

掃描二維碼
關注慕課網微信公眾號