我开始阅读http://www.albahari.com/threading/
上发布的信息提交人说:
Sleep(0)
或Yield在生产代码中偶尔会用于高级性能调整。它也是一个很好的诊断工具,可以帮助发现线程安全问题:如果在代码中的任何地方插入Thread.Yield()
会导致或破坏程序,那么几乎肯定会有错误。
根据MSDN on Thread.Yield()
,Thread.Yield()
的定义如下:
使调用线程执行到另一个准备在当前处理器上运行的线程。操作系统选择要生成的线程。
对我来说,这描述了一半的软件开发,即竞争条件无法解决。
这是线程中的标准调试实践吗?
答案 0 :(得分:2)
这是一个很好的建议,但我通常使用Sleep(1)。
使并发错误难以修复的一个原因是它们很难重现 - 大多数问题都会在您运气不好时出现,操作系统会在最糟糕的时间暂停您的线程。
当调试这样的问题时,你经常需要测试一个假设,例如"也许它会在我的线程被暂停时发生,并且......"。此时,您可以插入yield或sleep,这将暂停您的线程并大大增加重现错误的可能性。
答案 1 :(得分:1)
Thread.Yield将帮助您找到一些错误
你实际上在这里回答你自己的问题:
Sleep(0)或Yield在生产代码中偶尔会用于高级性能调整。它也是一个很好的诊断工具,可以帮助发现线程安全问题:如果在代码中的任何地方插入Thread.Yield()会破坏程序,你几乎肯定会有错误。
这里的关键是线程将处理器的权利放弃到另一个线程。
不要认为这是一个标准,但是如你所说的那样,它肯定是有用的,可以说....
多线程调试很难,没有真正的标准方法
调试多线程代码真的很难,有几个原因
用于观察程序的工具会修改它的执行方式,这意味着你并没有真正调试在生产中执行的内容。
添加允许您暂时观察应用程序状态的方法也需要同步(即Console.WriteLine
,这意味着代码与野外代码不同)
结果可能会因您执行的环境而异,示例您的开发框,其中包含 i5和8 GB的内存可能正常工作,但当您将程序上传到生产环境时, 16核和128 GB内存,您很可能会得到不同的结果
将所有东西扔到墙上,看看有什么粘贴
这不是一个很好的答案,但说实话是我最终调试很多时间的方式,你可以尝试这样的技巧:
编译为发布代码,优化可能会改变代码的执行顺序,导致Debug构建的说明与发布版本不同
这不是万无一失的,但认为这是一种很好的方法,可以确保您在向野外发布内容之前捕获尽可能多的问题。
答案 2 :(得分:0)
使用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;
}
}
如果存在作业并且实际从队列中检索作业,则这两者中的任何一个都将确保在测试之间不修改集合。确保尽可能快地释放锁定,因为否则您将会遇到锁争用问题,这可能会更加难以解决。永远不要将锁定放置到比完成工作所必需的更长的时间 - 在这种情况下,测试并从队列中检索项目。对所有共享资源执行此操作,您就不会再遇到任何竞争条件。
当然还有很多其他的线程问题,包括本质上不安全的方法。尽可能使您的线程自我依赖并锁定对共享资源的访问权限,您应该能够避免大部分讨厌的heisenbugs。