为了账号安全,请及时绑定邮箱和手机立即绑定

如何使用锁和委托来同步方法访问(线程安全)

如何使用锁和委托来同步方法访问(线程安全)

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 中并运行它,您将看到

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

如您所见,lock速度mutex

代码中的共享资源部分是如何确定的?

为了更好的性能调整,Visual Studio是否能够分析共享资源在哪里?

我已将我的 Visual Studio 2010更新到2015,在 Visual Studio 2015 中,当您查看每个方法的顶部时,您将看到参考如下图所示。

//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 中并运行它,您将得到如下所示的结果

//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 关注
  • 177 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信