【问题标题】:My multithread program works slowly or appear deadlock on dual core machine, please help我的多线程程序运行缓慢或在双核机器上出现死锁,请帮助
【发布时间】:2010-05-04 00:17:01
【问题描述】:

我有一个有多个线程的程序,一个线程在退出时会改变一个全局,另一个线程会反复轮询全局。对全局变量没有任何保护。 该程序在单处理器上运行良好。在双核机器上,它会工作一段时间,然后在 Sleep(0) 或 SuspendThread() 上停止。有人能帮我解决这个问题吗?

代码是这样的:

Thread 1:

do something...
while(1)
{
.....
flag_thread1_running=false;
SuspendThread(GetCurrentThread());
continue;

}

Thread 2
flag_thread1_running=true;
ResumeThread(thread1);
.....do some other work here....
while(flag_thread1_running) Sleep(0);
....

【问题讨论】:

  • 除非您是调试器,否则切勿使用 SuspendThread。相反,使用适当的同步对象(事件、互斥体、信号量等)来控制正在运行的线程。
  • 嗨拉森,这有什么原因吗?如果您能给我一些提示,将不胜感激
  • 这没有任何意义。标志在哪里设置为true?为什么while 循环的末尾有一个裸露的continue;?如果您生成一个演示问题的最小可编译程序,那么您可能会得到更好的帮助。
  • 线程 2 是这样的:flag_thread1_running=true;恢复线程(线程 1); .....在这里做一些其他的工作...... while(flag_thread1_running) Sleep(0); continue 语句让线程在恢复后从 while(1) 开始
  • 您能改写一下这个问题吗?我不确定您要做什么。

标签: c++ c multithreading winapi


【解决方案1】:

您在单处理器机器上看不到任何问题,但在多进程机器上看到问题,这是单处理器机器上线程上下文切换粒度相对较大的产物。在线程调度程序将执行切换到不同的线程之前,线程将执行 N 时间(毫秒、纳秒等)。许多 CPU 指令可以在典型的线程时间片中执行。您可以将其视为拥有相当大的“免费播放”独占处理器时间,在此期间您可能不会遇到资源冲突,因为处理器上没有执行任何其他操作。

但是,在多进程机器上运行时,两个线程中的 CPU 指令会同时执行。 “免费游戏”时间块的大小接近于零。

要重现两个线程之间的资源争用问题,您需要让线程 1 访问资源,并让线程 2 同时或几乎同时访问资源。

在单处理器机器上发生的大粒度线程切换中,线程切换恰好发生在正确位置的可能性很小,因此程序在单处理器机器上正常使用下可能永远不会出现故障。

在多进程机器中,指令在两个线程中同时执行,因此线程 1 和线程 2 同时访问同一资源的机会要大得多 - 比单处理器场景。

我已经多次看到这种情况发生:一个在 uniproc 机器上运行多年的应用程序在新的 multiproc 机器上执行时突然开始出现故障。原因是原始代码中存在潜在的线程错误,它根本不会在 uniproc 机器上遇到正确的时间片重合。

使用多线程代码时,绝对需要在多进程硬件上测试代码。如果您的代码中存在线程冲突问题,它们将很快出现在多进程机器上。

正如其他人所指出的,除非您是调试器,否则不要使用 SuspendThread()。使用互斥锁或其他同步对象在线程之间进行协调。

【讨论】:

  • +1 这听起来很像您在一台 BorCon 上进行的调试谈话(是“阅读茶叶”吗?)。我可以引用你的这个吗?
  • 是的,我很确定我会在“阅读茶叶”演讲中提到这一点。引用!
【解决方案2】:

尝试使用更像WaitForSingleObjectEx 的东西,而不是 SuspendThread。

【讨论】:

  • 当然。像这样使用 SuspendThread / ResumeThread 可能会导致难以调试竞态条件,这在多 CPU 系统上更频繁地打击你。必须使用适当的同步原语。另见stackoverflow.com/questions/131818/…
【解决方案3】:

您遇到了比赛条件。线程 2 可以执行 flag_thread1_running=true; 在线程 1 执行 flag_thread1_running=false 之前。

这不太可能在单 CPU 上发生,因为通常调度时间为 10-20 毫秒,您不太可能遇到问题。它也会在那里发生,但很少发生。

这里必须使用适当的同步原语。使用事件而不是布尔值。不要在循环中检查布尔值,而是使用 WaitForSingleObject(或 WaitForMultipleObjects 稍后进行更详细的操作)。

可以使用普通变量在线程之间执行同步,但这不是一个好主意,而且很难做到正确 - cf。 How can I write a lock free structure?。使用 Sleep、Suspend 或 Resume 执行调度绝对不是一个好主意。

【讨论】:

  • 我只是尝试了WaitforsingleObject并创建了一个事件并放弃了轮询机制。到目前为止,它在双核机器上运行良好。感谢您的有用提示
【解决方案4】:

我想你已经知道轮询全局标志是一个“坏主意™”,所以我将跳过那个简短的演讲。尝试将volatile 添加到标志声明中。这应该强制每次读取它都从内存中读取。如果没有volatile,实现可能会将标志读入寄存器而不是从内存中获取。

【讨论】:

  • 感谢 cmets。我已将全局变量定义为“易失性”,它没有用
  • 其实我看到了关于使用 volatile 关键字的不同意见。有人说没有用,会使程序变慢。到目前为止,我没有看到任何变化。
  • 为什么不直接使用原子变量?
  • 您好 WhirlWind,感谢您的建议。我还没有多线程编程的经验,但是,根据我的观点,没有必要对全局进行原子操作。没有对全局的并发写入。如果我错了,请纠正我
  • 有趣的是:至少在 x86 上,原子设置和读取操作编译为正常的内存指令。但是对原子类型进行了编码,因此编译器不会围绕它们的操作进行优化。
猜你喜欢
  • 2012-02-07
  • 1970-01-01
  • 2010-12-16
  • 1970-01-01
  • 2012-02-07
  • 2021-11-17
  • 2018-06-16
相关资源
最近更新 更多