什么情况下线程可以同时多次进入锁(监控)区?(问题修改):到目前为止,答案包括一个线程重新进入锁定区域,通过递归之类的东西,您可以在其中跟踪单个线程两次进入锁定所采取的步骤。但是有可能以某种方式,对于单个线程(可能来自ThreadPool,可能由于定时器事件或异步事件或线程进入休眠状态并在其他代码块中被唤醒/重用)以某种方式产生两个不同的地方是独立的彼此,那么当开发者不去思考,单纯阅读自己的代码时,会遇到锁重入问题吗?在ThreadPool类的remark(点这里)中,remark好像是说休眠的线程在不用的时候要重用,否则休眠就浪费了。但是在Monitor.Enter参考页面(单击此处)上,他们说“同一个线程在不阻塞的情况下多次调用Enter是合法的”。所以我想一定有一些我应该小心避免的事情。它是什么?单个线程如何进入同一个锁定区域两次?假设您有一些锁定区域,不幸的是很长一段时间。这可能是现实的,例如,如果您访问已被调出的内存(或其他东西)。锁定区域中的线程可能会进入睡眠状态。同一个线程是否有资格运行更多可能意外进入同一个锁定区域的代码?在我的测试中,以下不会导致同一线程的多个实例进入同一锁定区域。那么问题是如何产生的呢?您究竟需要注意避免什么?类myClass{私有对象myLockObject;公共myClass(){这个。myLockObject=新对象();int[]myIntArray=newint[100];//只需创建一堆东西,这样我就可以轻松启动一堆并行的东西Array.Clear(myIntArray,0,myIntArray.Length);//只需创建一堆东西,这样我就可以轻松启动一堆并行东西Parallel.ForEach(myIntArray,i=>MyParallelMethod());}privatevoidMyParallelMethod(){lock(this.myLockObject){Console.Error.WriteLine("ThreadId"+Thread.CurrentThread.ManagedThreadId.ToString()+"starting...");线程.睡眠(100);Console.Error.WriteLine("ThreadId"+Thread.CurrentThread.ManagedThreadId.ToString()+"完成。");}}}假设你有一个包含操作的队列:publicstaticQueueq=whatever;假设Queue有一个Dequeue方法返回一个布尔值,指示队列是否可以成功出队。假设你有一个循环:staticvoidMain(){q.Add(M);q.添加(M);动作动作;while(q.Dequeue(outaction))action();}staticobjectlockObject=newobject();staticvoidM(){动作动作;锁定(lockObject){如果(q.Dequeue(出行动))行动();}}很明显,主线程两次进入了M锁;此代码可重现输入。也就是说,它通过间接递归到自身。这段代码听起来不可思议吗?它不应该。Windows就是这样工作的。每个窗口都有一个消息队列,当消息队列被“抽出”时,这些消息对应的方法就会被调用。当点击按钮时,一条消息会进入消息队列;当队列被抽取时,将调用与该消息对应的点击处理程序。因此,编写Windows程序时非常常见且极其危险,其中锁包含对泵送消息循环的方法的调用。如果因为消息先被处理而进入那个锁,并且如果消息在队列中两次,代码将间接进入自己,这会导致各种疯狂。消除的方法是:(1)不要在锁里面做任何复杂的事情;(2)当你处理消息时,禁用处理程序直到消息被处理。如果您有这样的结构,则可以重新进入:ObjectlockObject=newObject();voidFoo(boolrecurse){lock(lockObject){Console.WriteLine("InLock");如果(递归){foo(假);}}}虽然这是一个非常简单的示例,但在许多情况下您可能会出现相互依赖或递归行为。例如:需要在同一个锁上重新进入同一个线程,以确保您不会与自己的代码发生死锁。您可以进入锁定块的更微妙的方法是GUI框架。例如,您可以在单个UI线程(Form类)上异步调用代码privateobjectlocker=newObject();publicvoidMethod(inta){lock(locker){this.BeginInvoke((MethodInvoker)(()=>Method(a)));}}当然,这也是死循环;您可能会遇到想要递归并且不会出现无限循环的情况。使用锁不是睡眠/唤醒线程的好方法。我只是使用任务并行库(TPL)等现有框架来简单地创建要创建的抽象任务(请参阅任务),底层框架负责创建新线程并在需要时休眠。恕我直言,重新进入锁不是你需要小心避免的事情(考虑到许多人对锁的心理模型,它充其量是危险的,请参阅下面的编辑)。文档的重点是解释线程不能使用Monitor.Enter阻塞自身。并非所有同步机制、框架和语言都是如此。有些具有不可重入同步,在这种情况下,您必须注意线程不会阻塞自身。您需要注意的是始终为每个Monitor.Enter调用调用Monitor.Exit。lock关键字会自动为您执行此操作。重入一个简单的例子:privateobjectlocker=newobject();publicvoidMethod(){lock(locker){lock(locker){Console.WriteLine("重新输入锁。");}}}Thread同一个对象被锁了两次,所以必须释放两次。通常它并不那么明显,并且有各种方法在同步对象上相互调用。关键是您不必担心线程会阻塞自己。也就是说,您通常应该尽量减少锁定所需的时间。获取锁在计算上并不昂贵,这与您可能听到的相反(大约为纳秒)。锁争用是昂贵的。编辑请阅读下面Eric的评论以获取更多详细信息,但摘要是当您看到锁时,您对它的解释应该是“此代码块的所有激活都与单个线程相关联”,而不是通常解释的那样,“此代码块的所有激活都作为单个原语单元执行”。例如:publicstaticvoidMain(){Method();}私有静态inti=0;私有静态对象储物柜=新对象();publicstaticvoidMethod(){lock(locker){intj=++i;if(i显然,程序崩溃了。没有锁,结果是一样的。危险在于锁会让你产生一种错误的安全感,即在初始化j和评估if之间没有任何东西可以改变你的状态。问题是你(可能是无意中)让Method进入自身,锁不会停止。正如Eric在他的回答中指出的,你可能直到有一天有人同时排队太多的动作才意识到这一点问题。ThreadPool线程不能在别处重用只是因为它们进入休眠状态,需要先完成才能被重用。长时间在锁定区域的线程无法在其他独立的控制点运行更多的代码。体验锁重入的唯一方法是递归或执行一个锁里面的方法或者委托,重新进入锁。让我们考虑递归以外的东西。在一些业务逻辑中,他们想要控制同步行为。其中一种模式,他们在一些CallMonitor.Enter某处,并且想稍后在其他地方调用Monitor.Exit。下面是获取相关想法的代码:publicpartialclassInfinity:IEnumerable{IEnumeratorIEnumerable.GetEnumerator(){returnthis.GetEnumerator();}publicIEnumeratorGetEnumerator(){for(;;)yieldreturn~0;}publicstaticreadonlyInfinityEnumerable=newInfinity();}publicpartialclassYourClass{voidReleaseLock(){for(;lockCount-->0;Monitor.Exit(yourLockObject));}voidGetLocked(){Monitor.Enter(yourLockObject);++锁定计数;}voidYourParallelMethod(intx){GetLocked();Debug.Print("lockCount={0}",lockCount);}publicstaticvoidPeformTest(){newThread(()=>{varthreadCurrent=Thread.CurrentThread;Debug.Print("ThreadId{0}starting...",threadCurrent.ManagedThreadId);varintanceOfYourClass=newYourClass();//Parallel.ForEach(Infinity.Enumerable,intanceOfYourClass.YourParallelMethod);foreach(variinEnumerable.Range(0,123))intanceOfYourClass.YourParallelMethod(i);intanceOfYourClass.ReleaseLock();Monitor.Exit(intanceOfYourClass.你的锁对象);//这里抛出SynchronizationLockExceptionDebug.Print("ThreadId{0}finished.",threadCurrent.ManagedThreadId);})。开始();}objectyourLockObject=newobject();int锁计数;如果您调用YourClass.PeformTest(),并获得大于1的lockCount,您将可以重入;不一定是并发的,如果重新进入不安全,您将陷入foreach循环。在Monitor.Exit(intanceOfYourClass.yourLockObject)将抛出SynchronizationLockException的代码块中,这是因为我们尝试调用Exit的次数多于它进入的次数。如果你打算使用lock关键字,你可能不会遇到这种情况,除非是直接或间接的递归调用。我想这就是提供lock关键字的原因:它可以防止Monitor.Exit被粗心地忽略。我注释掉了对Parallel.ForEach的调用,如果您有兴趣可以测试一下。要测试代码,.NetFramework4.0是您最少需要的,并且需要以下额外的命名空间:usingSystem.Threading.Tasks;使用系统诊断;使用系统线程;使用系统集合;玩得开心。以上就是C#学习教程:线程在什么情况下可以同时多次进入锁(监控)区?如果分享的所有内容对您有用,需要了解更多C#学习教程,希望您多多关注---本文收集自网络,不代表立场。如涉及侵权,请点击右侧联系管理员删除。如需转载请注明出处:
