第七色在线视频,2021少妇久久久久久久久久,亚洲欧洲精品成人久久av18,亚洲国产精品特色大片观看完整版,孙宇晨将参加特朗普的晚宴

為了賬號(hào)安全,請(qǐng)及時(shí)綁定郵箱和手機(jī)立即綁定
已解決430363個(gè)問題,去搜搜看,總會(huì)有你想問的

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

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

C#
楊魅力 2023-07-09 15:27:29
假設(shè)我們有這樣的方法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當(dāng)發(fā)生數(shù)千個(gè)并發(fā)調(diào)用時(shí)我們?nèi)绾巫龅竭@一點(diǎn)?可以delegate幫忙嗎?修改事件不是線程安全的,但調(diào)用委托是線程安全的。由于 Delegate 是不可變類型,因此它是線程安全的。這是否意味著delegate我的代碼是這樣的thread-safe?如果delegate不授予thread-safe并發(fā)調(diào)用。你能解釋一下為什么嗎?如果Lock受資助者thread-safe是這樣的話:如何避免Deadlock并在特定超時(shí)后釋放鎖?MutexLock在某些方面類似于。是Lock或者M(jìn)utex更快?為了更好的性能調(diào)整,DoseVisual Studio有能力分析共享資源在哪里嗎?
查看完整描述

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)行它,您將看到

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

如您所見,lock速度mutex

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

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

我已將我的 Visual Studio?2010更新到2015,在 Visual Studio 2015 中,當(dāng)您查看每個(gè)方法的頂部時(shí),您將看到參考如下圖所示。

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

當(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é)果

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

正如您所看到的,在第一個(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ā)異常的最佳方法。


查看完整回答
反對(duì) 回復(fù) 2023-07-09
?
瀟湘沐

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ù)?


查看完整回答
反對(duì) 回復(fù) 2023-07-09
?
繁星淼淼

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í)短暫地獲取鎖。


查看完整回答
反對(duì) 回復(fù) 2023-07-09
?
ABOUTYOU

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è)線程需要相同的鎖,則無法保證它們接收鎖的順序。


查看完整回答
反對(duì) 回復(fù) 2023-07-09
?
慕森王

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ì);您的共享資源是瓶頸。


查看完整回答
反對(duì) 回復(fù) 2023-07-09
  • 5 回答
  • 0 關(guān)注
  • 251 瀏覽

添加回答

舉報(bào)

0/150
提交
取消
微信客服

購課補(bǔ)貼
聯(lián)系客服咨詢優(yōu)惠詳情

幫助反饋 APP下載

慕課網(wǎng)APP
您的移動(dòng)學(xué)習(xí)伙伴

公眾號(hào)

掃描二維碼
關(guān)注慕課網(wǎng)微信公眾號(hào)