简体   繁体   English

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

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

I began reading through the information posted at http://www.albahari.com/threading/ 我开始阅读http://www.albahari.com/threading/上发布的信息

The author stated that: 提交人称:

Sleep(0) or Yield is occasionally useful in production code for advanced performance tweaks. Sleep(0)或Yield在生产代码中偶尔会用于高级性能调整。 It's also an excellent diagnostic tool for helping to uncover thread safety issues: if inserting Thread.Yield() anywhere in your code makes or breaks the program, you almost certainly have a bug. 它也是一个很好的诊断工具,可以帮助发现线程安全问题: 如果在代码中的任何地方插入Thread.Yield()会导致或破坏程序,那么几乎肯定会有错误。

According to the MSDN on Thread.Yield() , Thread.Yield() is defined as follows: 根据MSDN on Thread.Yield()MSDN on Thread.Yield()Thread.Yield()定义如下:

Causes the calling thread to yield execution to another thread that is ready to run on the current processor. 使调用线程执行到另一个准备在当前处理器上运行的线程。 The operating system selects the thread to yield to. 操作系统选择要生成的线程。

To me, this describes the half of software development that says that race conditions can't be solved. 对我来说,这描述了一半的软件开发,它说竞争条件无法解决。

Is this a standard debugging practice in threading? 这是线程中的标准调试实践吗?

It's good advice, but I usually use Sleep(1) instead. 这是一个很好的建议,但我通常使用Sleep(1)代替。

One of the things that makes concurrency bugs so hard to fix is that they are hard to reproduce -- most problems manifest when you are unlucky and the OS suspends your thread at the worst possible time. 使并发错误如此难以修复的一个原因是它们很难重现 - 大多数问题都表现在你不幸的时候,操作系统会在最糟糕的时候暂停你的线程。

When debugging problems like this, you'll often need to test a hypothesis like "maybe it happens when my thread gets suspended here, and...". 在调试这样的问题时,你经常需要测试一个假设,比如“当我的线程在这里暂停时可能会发生,并且......”。 At that point you can insert a yield or sleep, which will suspend your thread and greatly increase the likelihood of reproducing the error. 此时,您可以插入yield或sleep,这将暂停您的线程并大大增加再现错误的可能性。

Thread.Yield will help you find some bugs Thread.Yield将帮助您找到一些错误

You actually answer you're own question here : 你实际上在这里回答你自己的问题:

Sleep(0) or Yield is occasionally useful in production code for advanced performance tweaks. Sleep(0)或Yield在生产代码中偶尔会用于高级性能调整。 It's also an excellent diagnostic tool for helping to uncover thread safety issues: if inserting Thread.Yield() anywhere in your code makes or breaks the program, you almost certainly have a bug. 它也是一个很好的诊断工具,可以帮助发现线程安全问题: 如果在代码中的任何地方插入Thread.Yield()会导致或破坏程序,那么几乎肯定会有错误。

The key here is that the thread forfeits the right to the processor to a different thread. 这里的关键是线程将处理器的权利放弃到另一个线程。

Don't think it is a standard but it is definitely useful, as mentioned in the quote you included, that being said.... 不要认为它是一个标准,但它确实是有用的,正如你所包含的引用中所提到的那样......

Multithreaded Debugging is Hard and there is no real standard way to do it 多线程调试很难,没有真正的标准方法

It is really hard to debug a multithreaded code, there are a few reasons why 调试多线程代码真的很难,原因有几个

  1. Tools used to observe the program modify the way it is executing, which means you aren't really debugging what would be executed in production. 用于观察程序的工具会修改它的执行方式,这意味着您并没有真正调试将在生产中执行的操作。

  2. Adding methods that let you observe the state of the application temporarily needs to be synchronized as well (ie Console.WriteLine , which means different code than what is in the wild ) 暂时需要同步添加允许您观察应用程序状态的方法(即Console.WriteLine ,这意味着代码与野外代码不同)

  3. Results can vary wildly depending on the environment you are executing on, example your dev box with an i5 and 8 gigs of RAM might work fine but when you upload your program to the production environment with 16 cores and 128 gigs of RAM you will most likely get different results 根据您正在执行的环境,结果可能会有很大差异, 例如 ,带有i5和8 GB RAM的 开发盒可能会正常工作,但是当您将程序上传到具有16个内核和128 GB内存生产环境时 ,您很可能得到不同的结果

Just throw everything against the wall and see what sticks 把所有东西扔在墙上,看看有什么东西

This isn't a nice answer but to be honest is the way I end up debugging a lot of time, you can try techniques like : 这不是一个很好的答案,但说实话是我最终调试很多时间的方式,你可以尝试以下技术:

Compile as Release code, optimizations could change execution order of code and result in instructions for Debug build being different than Release build 编译为发布代码,优化可能会更改代码的执行顺序,并导致Debug构建的指令与发布版本不同

  1. Running multithreaded code many times (depending on intensity between 1,000 and 1,000,000 times ) 多次运行多线程代码(取决于1,000到1,000,000次之间的强度)
  2. Calling only a few times 只打几次电话
  3. Call only one time ( might seem stupid but this has messed with me a few times) 只打了一次电话(可能看起来很愚蠢,但这几次搞砸了我)
  4. Call multiple times over a long period of time ( every hour make a call for a day) 长时间多次通话(每小时拨打一天电话)

This isn't fool proof but think this is a good way to ensure you capture as many issues as you can before releasing something into the wild. 这不是万无一失的,但认为这是一种很好的方法,可以确保在释放出一些东西之前捕获尽可能多的问题。

Using Thread.Sleep() or Thread.Yield() won't solve your bugs, but they might hide them in some cases. 使用Thread.Sleep()Thread.Yield()不会解决您的错误,但在某些情况下它们可能会隐藏它们。 While this seems like a good thing - stopping bugs from popping up is better than having them kill your program - the reality is that you're not resolving the underlying issue. 虽然这似乎是件好事 - 阻止弹出错误比让他们杀死你的程序更好 - 现实是你没有解决潜在的问题。

Yes, stomping bugs in a multi-threaded program can be damned hard. 是的,在多线程程序中踩踏错误可能会很难受。 This is where you really have to understand how your threads are interacting and what happens when you have threads running simultaneously on different CPU cores, etc. Without that understanding you'll likely never find the error in your program logic that is causing the problem in the first place. 这是你真正需要了解线程如何交互以及当线程在不同的CPU内核上同时运行时会发生什么等等。如果没有这种理解,你可能永远不会在程序逻辑中发现导致问题的错误。第一名。

When writing a multi-threaded program you have to make sure that every operation on shared data is atomic. 编写多线程程序时,必须确保共享数据上的每个操作都是原子的。 Even a simple increment operation becomes a problem when you are doing it on a shared value, which is why we have the Interlocked.Increment() method. 当您在共享值上执行此操作时,即使是简单的增量操作也会成为问题,这就是我们使用Interlocked.Increment()方法的原因。 For everything else there are locks and so on to help you manage your thread interactions. 对于其他所有东西,都有锁等等,以帮助您管理线程交互。

Examine every interaction that your threads have with shared data and make sure that there is a lock in place on the data while you are using it. 检查线程与共享数据之间的每次交互,并确保在使用数据时数据上存在锁定。 For instance, let's say you're queuing jobs for a set of worker threads to do: 例如,假设您正在为一组工作线程排队工作:

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();
        }
    }
}

Seems simple enough, and it's only going to be a few cycles between when you check for a job and then go to fetch it. 看起来很简单,只有在你检查一份工作然后再去取它之间的几个周期。 In the meantime another thread has swooped in and grabbed the waiting job out from under you. 与此同时,另一个线程突然进入并抓住了你下面的等待工作。 You can solve this in a few ways, but the simplest is probably to use a thread-safe version of the queue from System.Collections.Concurrent : 您可以通过几种方式解决此问题,但最简单的方法是使用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();
        }
    }
}

In cases where you don't have a thread-safe version you'll have to fall back on either locking or some other mechanism to secure your access to the shared data. 如果您没有线程安全版本,则必须依靠锁定或其他一些机制来保护您对共享数据的访问。 A lock-based solution to the above might look something like this: 基于锁定的上述解决方案可能如下所示:

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;
    }
}

Either of those two will ensure that the collection isn't modified between testing if there is a job and actually retrieving the job from the queue. 如果存在作业并且实际从队列中检索作业,则这两者中的任何一个都将确保在测试之间不修改集合。 Make sure you release the locks as fast as you can though, because otherwise you're going to have lock contention issues which can be a whole lot tougher to resolve. 确保尽可能快地释放锁定,因为否则您将遇到锁定争用问题,这可能会更加难以解决。 Never leave a lock in place longer than absolutely necessary to do the work - in this case, test for and retrieve an item from the queue. 永远不要将锁定放置到比完成工作所必需的更长的时间 - 在这种情况下,测试并从队列中检索项目。 Do this for all of your shared resources and you won't have any more race conditions on them. 对所有共享资源执行此操作,您将不再拥有任何竞争条件。

Of course there are plenty of other threading issues, including methods that are inherently thread unsafe. 当然还有很多其他的线程问题,包括本质上不安全的方法。 Make your threads as self-dependent as you can and lock access to shared resources and you should be able to avoid most of the nasty heisenbugs . 让你的线程尽可能地自我依赖并锁定对共享资源的访问权限,你应该能够避免大多数令人讨厌的heisenbug

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

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