找出C#中阻塞线程的原因

时间:2013-09-26 22:29:01

标签: c# multithreading

所以我有一个游戏,其中主线程执行您期望的常用更新/渲染逻辑,以及第二个执行非常强烈处理的线程。我遇到的问题是偶尔主线程将被中断,游戏将降至60FPS以下。我很确定它被其他线程阻止,但由于没有明确的锁定,我无法证明它。

有几种情况我可以想到为什么主线程会被辅助线程阻止:

  • 第二个线程分配了很多小对象;内存分配强制一个线程等待,而另一个线程分配内存。这似乎不太可能,因为你希望在分配一个小对象后,主线程可以继续分配它需要的东西。
  • 某种形式的JIT优化,可以防止辅助线程在花费太长时间时被中断。这毫无意义。
  • 某种被锁定的跨线程引用。不太可能,因为代码是由一个队列故意分开的,其中辅助线程从队列中获取项目,但不会锁定和阻止项目被放置在队列中。
  • 操作系统的线程优先级错误,因为在Linux和Windows上都会出现此问题,所以也不太可能。

我已经尝试过安装秒表并测量哪些代码区域需要时间,但这并没有真正告诉我“主线程随机停止500ms”;它实际上并没有告诉我是否有长时间阻塞主线程的锁。

我是否可以使用任何技术来缩小此问题的原因?

-----编辑-----

这些是运行Mono分析器并报告锁争用的结果:

Monitor lock summary
    Lock object 0x7f05190c9fe0: 1 contentions
            0.002126 secs total wait time, 0.002126 max, 0.002126 average
    1 contentions from:
            (wrapper runtime-invoke) object:runtime_invoke_void__this__ (object,intptr,intptr,intptr)
            System.Threading.Thread:StartInternal ()
            System.Threading.Timer/Scheduler:SchedulerThread ()
            (wrapper unknown) System.Threading.Monitor:FastMonitorEnterV4 (object,bool&)
            System.Threading.Monitor:Enter (object,bool&)
            System.Threading.Monitor:TryEnter (object,int,bool&)
            (wrapper managed-to-native) System.Threading.Monitor:try_enter_with_atomic_var (object,int,bool&)
    Lock object 0x7f051910b100: 1 contentions
            0.000628 secs total wait time, 0.000628 max, 0.000628 average
    1 contentions from:
            Ninject.Components.ComponentContainer:Get (System.Type)
            Ninject.Components.ComponentContainer:ResolveInstance (System.Type,System.Type)
            Ninject.Components.ComponentContainer:CreateNewInstance (System.Type,System.Type)
            System.Reflection.ConstructorInfo:Invoke (object[])
            System.Reflection.MonoCMethod:Invoke (System.Reflection.BindingFlags,System.Reflection.Binder,object[],System.Globalization.CultureInfo)
            System.Reflection.MonoCMethod:DoInvoke (object,System.Reflection.BindingFlags,System.Reflection.Binder,object[],System.Globalization.CultureInfo)
            System.Reflection.MonoCMethod:InternalInvoke (object,object[])
            (wrapper managed-to-native) System.Reflection.MonoCMethod:InternalInvoke (System.Reflection.MonoCMethod,object,object[],System.Exception&)
            (wrapper runtime-invoke) <Module>:runtime_invoke_void__this___object (object,intptr,intptr,intptr)
            Ninject.Activation.Caching.ActivationCache:.ctor (Ninject.Activation.Caching.ICachePruner)
            Ninject.Activation.Caching.GarbageCollectionCachePruner:Start (Ninject.Activation.Caching.IPruneable)
            (wrapper remoting-invoke-with-check) System.Threading.Timer:.ctor (System.Threading.TimerCallback,object,int,int)
            System.Threading.Timer:.ctor (System.Threading.TimerCallback,object,int,int)
            System.Threading.Timer:Init (System.Threading.TimerCallback,object,long,long)
            System.Threading.Timer:Change (long,long,bool)
            System.Threading.Timer/Scheduler:Change (System.Threading.Timer,long)
            (wrapper unknown) System.Threading.Monitor:FastMonitorEnterV4 (object,bool&)
            System.Threading.Monitor:Enter (object,bool&)
            System.Threading.Monitor:TryEnter (object,int,bool&)
            (wrapper managed-to-native) System.Threading.Monitor:try_enter_with_atomic_var (object,int,bool&)
    Lock object 0x7f05190ca000: 1 contentions
            0.000347 secs total wait time, 0.000347 max, 0.000347 average
    1 contentions from:
            (wrapper runtime-invoke) object:runtime_invoke_void__this__ (object,intptr,intptr,intptr)
            System.Threading.Thread:StartInternal ()
            System.Threading.Timer/Scheduler:SchedulerThread ()
            (wrapper remoting-invoke-with-check) System.Threading.EventWaitHandle:Reset ()
            System.Threading.EventWaitHandle:Reset ()
            (wrapper unknown) System.Threading.Monitor:FastMonitorEnterV4 (object,bool&)
            System.Threading.Monitor:Enter (object,bool&)
            System.Threading.Monitor:TryEnter (object,int,bool&)
            (wrapper managed-to-native) System.Threading.Monitor:try_enter_with_atomic_var (object,int,bool&)
    Lock contentions: 3
    Lock acquired: 3
    Lock failures: 0

这是从运行游戏大约20-30秒,在此期间我观察到至少10个滞后峰值。在那段时间内只有3个锁定争用,所有这些争用都需要不到16毫秒来解决。

1 个答案:

答案 0 :(得分:4)

您正在使用垃圾收集平台来运行实时应用程序,从这一点来看,您不能期望有太多预测性。垃圾收集系统在空间用完时锁定所有正在运行的线程,通过使用3代“最近性”数据作为spaning树发现优化系统,从“根对象”中移动“可跨越的树”来清理悬空对象。但无论如何,主线程中存在或不存在同步都不会阻止它不时被停止。

其次,渲染在交换功能上阻塞(“以屏幕显示”,在直接的3d术语中),这是一个等待“第三个最旧的帧完成渲染,最后一个渲染命令列表被刷新,以及VSync”的函数收到信号“在它让主线程继续之前”。您可以尝试在交换呼叫周围进行分析,以检查您的驱动程序是否与锁定有关。

第三,OS调度程序,就像你提到的那样,是先发制人的,每个内核标记,在1到15ms之间,你可以得到上下文切换。如果你的Linux比内核V 3.1更新(或者等于),那么你将拥有内核构建选项FULL_DYN_TICKS,当整个系统上只有一个任务处于活动状态时,它会禁用抢先中断计时器,但是我觉得这是一种托管语言像这样的要求不太可能得到满足。 但是,500ms代表非常长的时间,33个滴答,这只有在你有33个其他任务在同一时间以相同的prirority运行完整CPU时才会发生。不太可能。

出于温度原因,你可能有硬件决定来限制你的CPU,或者就此而言,还有GPU。

您可以拥有一个共享图形卡内存和泄漏的复合桌面,这会强制驱动程序不时地在主内存中交换纹理。这种错误在Linux上发生了很多,特别是对于像“祖母绿”,“compiz”等“危险”的桌面。

检查另一个3D应用程序并查看行为,完全停止工作线程,看看它是否有助于主线程的健全性。检查小对象分配和旧对象分配。第一代垃圾收集可能很重要。

祝你好运