【问题标题】:Deadlock, some questions死锁,一些问题
【发布时间】:2010-12-30 04:03:53
【问题描述】:

在对我的任务调度程序进行了某个阶段的测试后,我几乎随机遇到了死锁。我想寻求一些帮助,特别是我想知道我的方法是否会陷入僵局,或者问题是否出在其他地方。
在开始之前,我会说该应用程序是一个全屏游戏。 (以防它可能影响任何事情)
我会用文字解释系统是如何工作的。

任务调度器

1) 在每帧开始时为每个 CPU 调度 2 个任务。 (通过调度我的意思是将任务的私有 WaitHandle 设置为允许任务做一些工作)

这是一个简短的代码,总结了系统对这两个任务中的每一个所做的事情。

                Scheduled++;
                InternalLock.Reset();

所有两个任务都安排好后,通过设置私有 WaitHandle 来启动它们。

2) 等待所有任务完成。等待是通过等待每个任务必须发出信号的内部 WaitHandle 来完成的。 (WaitOne() 在每个任务上)

这是等待的代码:

    if (Attese.Length > 0)
    {
        TmpIsSucceded = false;
        for (int i = 0; i < Attese.Length; i++)
        {
            WaitHandle actual = Attese[i].Wait;
            do
            {
                TmpIsSucceded = actual.WaitOne(150);
                if (!TmpIsSucceded)
                    EnginesManager.ProcessMessages();
            } while (!TmpIsSucceded);
        }
    }

任务

1) 直到游戏结束才关闭。
2) 有 2 个内部 WaitHandle。一位私人告诉他有工作时的任务。当任务结束其工作时发出的一个内部信号。 (任务调度程序正在等待的那个)
3) 当任务自己完成时,在任务调度程序的同步(通过 lock())队列中启动另一个可用的任务(以相同的方式通过设置该任务的私有等待句柄)。

这是任务的主循环:

       private void CoreThread()
       {
            while (_active)
            {
                PrivateLock.WaitOne(-1, false);
                while (Scheduled > 0)
                {
                    if (OnThreadExecute != null)
                        OnThreadExecute(this, null);
                    Scheduled--;
                    if (Scheduled == 0)
                    {
                        PrivateLock.Reset();
                        if (OnThreadEnd != null)
                            OnThreadEnd(this, null);
                        InternalLock.Set();
                    }
                }
            }
        }

InternalLock 和 PrivateLock 是两个等待句柄。请注意,InternalLock 等待句柄仅在此代码中设置。没有其他地方设置了 InternalLock 或 PrivateLock。 (除了我贴的代码)

当死锁发生时,任务调度程序正在等待所有任务完成,但其中一项任务从未设置 InternalLock 等待句柄。 “阻塞”任务在“PrivateLock.WaitOne(-1, false);”处停止发生死锁时的行。

有人知道这个僵局吗?

编辑:

    internal void StartSchedule()
    {
        for (int i = 0; i < Tasks.Length; i++)
        {
            if (Tasks[i].Schedule())
                QTasks.Enqueue(Tasks[i]);
        }
        StartThreadAvailable();
    }

    private void StartThreadAvailable()
    {
        TempExecList.Clear();
        for (int i = 0; i < NThread; i++)
        {
            if (QTasks.Count > 0)
                TempExecList.Add(QTasks.Dequeue());
        }
        Int32 count = TempExecList.Count;
        for (int i = 0; i < count; i++)
            TempExecList[i].StartThread();
    }

    internal void StartThread()
    {
        PrivateLock.Set();
    }

这是按要求调用私有句柄的 Set() 的代码。

Schedule() 在这种情况下总是返回 true。 (它只是将任务的计划变量加1并重置InternalLock)

编辑 2:

这是所要求的 2 个类的代码:

http://pastebin.com/m225f839e(游戏任务)

http://pastebin.com/m389629cd(任务调度器)

【问题讨论】:

  • 请出示包含PrivateLock.Set()的代码
  • 你是如何声明等待句柄的?你能发布一个完整的编译示例吗?
  • 如果您想要TaskScheduler 和GameTask 这两个类,我可以发帖,但正如我所说,这是一个非常随机的错误,因此您不能依赖运行几个小时后可能会失败的示例。 XD
  • 你需要用一个测试用例打破代码以隔离棘手的东西,但你绝对需要清理同步,你正在从多个未同步的线程访问状态
  • 虽然我没有答案,但我觉得有义务指出“用保护替换嵌套”重构 - refactoring.com/catalog/…

标签: c# .net multithreading deadlock


【解决方案1】:

Scheduled 的实现未显示,但在我看来,该变量的增量器和减量器之间可能存在竞争。我想你可能需要在这里使用InterlockedDecrement,如果在 Scheduled > 0 的测试和 Scheduled == 0 的测试之间没有空间,我也会更舒服。

更多这样的

PrivateLock.WaitOne(-1, false);
while (true)
{
    // fetch Sched, so the whole loop sees a single value
    int iSched = Scheduled--; // implementation should be Interlocked.Decrement()
    if (iSched <= 0)
    {
       if (iSched < 0)
       {
          // should never get here, throw exception?
       }
       PrivateLock.Reset();                
       if (OnThreadEnd != null)                
           OnThreadEnd(this, null);                
       InternalLock.Set();                
       break; // break out of while
    }

    if (OnThreadExecute != null)              
       OnThreadExecute(this, null);
}

【讨论】:

  • 我添加了整个代码,同时我会尝试你的建议。
【解决方案2】:

我发现了问题。这是一个非常愚蠢的...

我会解释,以便其他遇到此问题的人可以利用我失去的时间。 XD 无论如何,非常感谢 John Knoeller 帮助我通过其洞察力缩小问题范围。 :D

让我们来看看主线程。 InternalLock.Set() 在任务调度程序线程上设置块并说“继续”。假设任务调度器中的任务只有1。想象一下这种可能的情况

1) 第一步

任务计划 - 计划
任务 1 - 等待

2) 第二步

任务计划程序 - 等待
任务 1 - 工作

3) 第三步

任务计划程序 - 等待
任务 1 - InternalLock.Set();

4) 第四步

任务计划 - 计划
任务 1 - |当(计划 > 0)|

在第四步导致主线程中的 SCHEDULE 增加了调度变量。这样,while 并没有结束,导致代码中的所有干扰(和死锁)。 我通过在 InternalLock.Set(); 之后简单地添加一个中断来修复。 现在没有问题了。

(对于那些说我在没有同步的情况下访问状态的人。请注意,调度函数在任何线程都得到工作之前只调用一次,所以它是否同步并不重要。这个问题真的很愚蠢。我的错。:D)

谢谢!!!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-03-28
    • 2012-04-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-07-02
    • 2019-10-18
    • 2015-03-29
    相关资源
    最近更新 更多