繁体   English   中英

Thread.Yield是一种标准方法,用于确定您的多线程应用程序C#中是否存在错误

[英]Is Thread.Yield a standard way to determine if you have a bug in your multi threaded application C#

我开始阅读http://www.albahari.com/threading/上发布的信息

提交人称:

Sleep(0)或Yield在生产代码中偶尔会用于高级性能调整。 它也是一个很好的诊断工具,可以帮助发现线程安全问题: 如果在代码中的任何地方插入Thread.Yield()会导致或破坏程序,那么几乎肯定会有错误。

根据MSDN on Thread.Yield()MSDN on Thread.Yield()Thread.Yield()定义如下:

使调用线程执行到另一个准备在当前处理器上运行的线程。 操作系统选择要生成的线程。

对我来说,这描述了一半的软件开发,它说竞争条件无法解决。

这是线程中的标准调试实践吗?

这是一个很好的建议,但我通常使用Sleep(1)代替。

使并发错误如此难以修复的一个原因是它们很难重现 - 大多数问题都表现在你不幸的时候,操作系统会在最糟糕的时候暂停你的线程。

在调试这样的问题时,你经常需要测试一个假设,比如“当我的线程在这里暂停时可能会发生,并且......”。 此时,您可以插入yield或sleep,这将暂停您的线程并大大增加再现错误的可能性。

Thread.Yield将帮助您找到一些错误

你实际上在这里回答你自己的问题:

Sleep(0)或Yield在生产代码中偶尔会用于高级性能调整。 它也是一个很好的诊断工具,可以帮助发现线程安全问题: 如果在代码中的任何地方插入Thread.Yield()会导致或破坏程序,那么几乎肯定会有错误。

这里的关键是线程将处理器的权利放弃到另一个线程。

不要认为它是一个标准,但它确实是有用的,正如你所包含的引用中所提到的那样......

多线程调试很难,没有真正的标准方法

调试多线程代码真的很难,原因有几个

  1. 用于观察程序的工具会修改它的执行方式,这意味着您并没有真正调试将在生产中执行的操作。

  2. 暂时需要同步添加允许您观察应用程序状态的方法(即Console.WriteLine ,这意味着代码与野外代码不同)

  3. 根据您正在执行的环境,结果可能会有很大差异, 例如 ,带有i5和8 GB RAM的 开发盒可能会正常工作,但是当您将程序上传到具有16个内核和128 GB内存生产环境时 ,您很可能得到不同的结果

把所有东西扔在墙上,看看有什么东西

这不是一个很好的答案,但说实话是我最终调试很多时间的方式,你可以尝试以下技术:

编译为发布代码,优化可能会更改代码的执行顺序,并导致Debug构建的指令与发布版本不同

  1. 多次运行多线程代码(取决于1,000到1,000,000次之间的强度)
  2. 只打几次电话
  3. 只打了一次电话(可能看起来很愚蠢,但这几次搞砸了我)
  4. 长时间多次通话(每小时拨打一天电话)

这不是万无一失的,但认为这是一种很好的方法,可以确保在释放出一些东西之前捕获尽可能多的问题。

使用Thread.Sleep()Thread.Yield()不会解决您的错误,但在某些情况下它们可能会隐藏它们。 虽然这似乎是件好事 - 阻止弹出错误比让他们杀死你的程序更好 - 现实是你没有解决潜在的问题。

是的,在多线程程序中踩踏错误可能会很难受。 这是你真正需要了解线程如何交互以及当线程在不同的CPU内核上同时运行时会发生什么等等。如果没有这种理解,你可能永远不会在程序逻辑中发现导致问题的错误。第一名。

编写多线程程序时,必须确保共享数据上的每个操作都是原子的。 当您在共享值上执行此操作时,即使是简单的增量操作也会成为问题,这就是我们使用Interlocked.Increment()方法的原因。 对于其他所有东西,都有锁等等,以帮助您管理线程交互。

检查线程与共享数据之间的每次交互,并确保在使用数据时数据上存在锁定。 例如,假设您正在为一组工作线程排队工作:

public class WorkerThread
{
    public static readonly Queue<Job> jobs = new Queue<Job>();

    public void ThreadFunc()
    {
        while (true)
        {
            if (jobs.Count > 0)
            {
                Job myJob = jobs.Dequeue()
                // do something with the job...
            }
            else
                Thread.Yield();
        }
    }
}

看起来很简单,只有在你检查一份工作然后再去取它之间的几个周期。 与此同时,另一个线程突然进入并抓住了你下面的等待工作。 您可以通过几种方式解决此问题,但最简单的方法是使用System.Collections.Concurrent的线程安全版本的队列:

public class WorkerThread
{
    public static readonly ConcurrentQueue<Job> jobs = new ConcurrentQueue<Job>();

    public void ThreadFunc()
    {
        Job myJob;
        while (true)
        {
            if (jobs.TryDequeue(out myJob))
            {
                // do something with the job...
            }
            else
                Thread.Yield();
        }
    }
}

如果您没有线程安全版本,则必须依靠锁定或其他一些机制来保护您对共享数据的访问。 基于锁定的上述解决方案可能如下所示:

public class WorkerThread
{
    private static object _jobs_lock = new object();
    private static readonly Queue<Job> _jobs = new Queue<Job>();

    public void ThreadFunc()
    {
        Job myJob;
        while (true)
        {
            if ((myJob = NextJob()) != null)
            {
                // do something with the job...
            }
            else
                Thread.Yield();
        }
    }

    public void AddJob(Job newJob)
    {
        lock(_jobs_lock)
            _jobs.Enqueue(newJob);
    }

    private Job NextJob()
    {
        lock (_jobs_lock)
        {
            if (_jobs.Count > 0)
                return _jobs.Dequeue();
        }
        return null;
    }
}

如果存在作业并且实际从队列中检索作业,则这两者中的任何一个都将确保在测试之间不修改集合。 确保尽可能快地释放锁定,因为否则您将遇到锁定争用问题,这可能会更加难以解决。 永远不要将锁定放置到比完成工作所必需的更长的时间 - 在这种情况下,测试并从队列中检索项目。 对所有共享资源执行此操作,您将不再拥有任何竞争条件。

当然还有很多其他的线程问题,包括本质上不安全的方法。 让你的线程尽可能地自我依赖并锁定对共享资源的访问权限,你应该能够避免大多数令人讨厌的heisenbug

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM