4

所以我有一个游戏,其中主线程执行您期望的通常的更新/渲染逻辑,而第二个线程执行一些非常密集的处理。我遇到的问题是偶尔主线程会被中断,游戏会降到 60FPS 以下。我相当确定它被另一个线程阻塞了,但是由于没有显式锁定,我无法证明它。

关于为什么主线程会被辅助线程阻塞,我可以想到几种情况:

  • 第二个线程分配了很多小对象;内存分配迫使一个线程等待,而另一个线程分配内存。这似乎不太可能,因为您期望在分配一个小对象之后,主线程可以继续分配它需要的东西。
  • 某种形式的 JIT 优化可防止辅助线程在耗时过长时被中断。这根本没有意义。
  • 某种被锁定的跨线程引用。不太可能,因为代码被一个队列有意分隔,在该队列中辅助线程从队列中取出项目,但不锁定并阻止项目被放置在队列中。
  • 操作系统的错误线程优先级,也不太可能,因为这个问题发生在 Linux 和 Windows 上。

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

有什么技术可以用来缩小这个问题的原因吗?

- - - 编辑 - - -

这些是运行 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 毫秒的时间来解决。

4

1 回答 1

4

您正在使用垃圾收集平台来运行实时应用程序,从这一点上您不能期望太多的预测性。垃圾收集系统在空间不足时锁定所有正在运行的线程,以通过“从根对象遍历可生成树”来清理悬空对象,使用 3 代“最近”数据作为生成树发现优化系统。但是在任何情况下,主线程中同步的存在或不存在都不会阻止它不时停止。

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

第三,操作系统调度程序,就像你提到的,是抢先的,每个内核滴答声,即 1 到 15 毫秒之间,你可以切换上下文。如果您有一个比内核 V 3.1 更新(或相等)的 linux,您将拥有内核构建选项 FULL_DYN_TICKS 当整个系统上只有一个任务处于活动状态时禁用抢先式中断计时器,但我觉得使用托管语言像这样的要求不太可能满足。然而,500ms 代表了一个非常长的时间,33 个滴答声,只有当你有 33 个其他任务同时以相同的优先级运行全 CPU 时才会发生这种情况。也不太可能。

由于温度原因,您可能会做出硬件决定来限制 CPU,或者 GPU 也是如此。

您可能有一个共享图形卡内存和泄漏的复合桌面,这会迫使驱动程序不时交换主内存中的纹理。这种错误在 linux 上经常发生,尤其是对于像 emerald、compiz 等“危险”的桌面。

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

祝你好运

于 2013-09-27T00:43:13.713 回答