是线程.产生一种标准的方法来确定您的多线程应用程序C#中是否存在错误



我开始阅读发布在http://www.albahari.com/threading/

提交人说:

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

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

使调用线程将执行权交给准备在当前处理器上运行的另一个线程。操作系统选择要屈服的线程。

对我来说,这描述了软件开发的一半,即种族条件无法解决。

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

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

并发错误很难修复的一个原因是它们很难重现——大多数问题都会在你运气不好的时候出现,操作系统会在最糟糕的时候挂起你的线程。

当调试这样的问题时,你通常需要测试一个假设,比如"也许这发生在我的线程被挂起的时候,然后…"。这时你可以插入一个yield或sleep,这将挂起你的线程,并大大增加重现错误的可能性。

使用Thread.Sleep()Thread.Yield()不会解决您的错误,但在某些情况下它们可能会隐藏它们。虽然这看起来是一件好事——阻止bug出现总比让它们杀死你的程序要好——但现实是你没有解决根本问题。

是的,在多线程程序中消除bug可能非常困难。这就是你真正需要了解线程是如何交互的,以及当线程在不同的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;
    }
}

这两者中的任何一个都将确保在测试是否有作业和实际从队列中检索作业之间不会修改集合。不过,请确保尽可能快地释放锁,否则您将遇到锁争用问题,这可能更难解决。永远不要将锁留在原地超过完成工作所必需的时间——在这种情况下,测试并从队列中检索项目。对你所有的共享资源都这样做,你就不会有更多的比赛条件了。

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

线程。Yield将帮助您找到一些错误

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

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

这里的关键是线程放弃了处理器对另一个线程的访问权。

不要认为这是一个标准,但它肯定是有用的,正如你在引用中提到的那样。。。。

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

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

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

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

  3. 结果可能会根据您执行的环境而变化很大,示例您的开发框具有i5和8吉的RAM可能工作正常,但当您将程序上传到生产环境(具有16核和128吉的RAM)时,您很可能会得到不同的结果

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

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

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

  1. 多次运行多线程代码(取决于1000到1000000次之间的强度)
  2. 只打了几次电话
  3. 只打一次电话(可能看起来很愚蠢,但这已经让我困扰了好几次)
  4. 长时间多次通话(一天内每小时打一个电话)

这不是傻瓜式的,但我认为这是一个很好的方法,可以确保你在将东西放归野外之前尽可能多地捕捉问题。

最新更新