[英]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()会导致或破坏程序,那么几乎肯定会有错误。
这里的关键是线程将处理器的权利放弃到另一个线程。
不要认为它是一个标准,但它确实是有用的,正如你所包含的引用中所提到的那样......
多线程调试很难,没有真正的标准方法
调试多线程代码真的很难,原因有几个
用于观察程序的工具会修改它的执行方式,这意味着您并没有真正调试将在生产中执行的操作。
暂时需要同步添加允许您观察应用程序状态的方法(即Console.WriteLine
,这意味着代码与野外代码不同)
根据您正在执行的环境,结果可能会有很大差异, 例如 ,带有i5和8 GB RAM的 开发盒可能会正常工作,但是当您将程序上传到具有16个内核和128 GB内存的生产环境时 ,您很可能得到不同的结果
把所有东西扔在墙上,看看有什么东西
这不是一个很好的答案,但说实话是我最终调试很多时间的方式,你可以尝试以下技术:
编译为发布代码,优化可能会更改代码的执行顺序,并导致Debug构建的指令与发布版本不同
这不是万无一失的,但认为这是一种很好的方法,可以确保在释放出一些东西之前捕获尽可能多的问题。
使用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.