【问题标题】:Handling windows events in a tight loop?在一个紧密的循环中处理 Windows 事件?
【发布时间】:2011-03-16 10:57:58
【问题描述】:

我为脚本语言编写了编译器和解释器。解释器是一个 DLL(“引擎”),它在单个线程中运行,可以加载许多 100 或 1000 的已编译字节码应用程序,并将它们作为一组内部进程执行。有一个主循环,在将一个加载到下一个进程之前,从每个加载的应用进程中执行一些指令。

编译后的应用程序中的字节码指令可以是低级指令(pop、push、add、sub 等),也可以是对外部函数库的调用(大部分工作都在这里完成)。这些外部库可以回调引擎使内部进程进入睡眠状态,等待外部函数(可能在接收到事件之后)再次唤醒内部进程的特定事件。如果所有内部进程都处于睡眠状态(大多数情况下都是这样),那么我也可以让引擎进入睡眠状态,从而将 CPU 交给其他线程。

但是,没有什么可以阻止某人编写一个像这样执行紧密循环的脚本:

当(1) x=1; 结束

这意味着我的主循环永远不会进入睡眠状态,因此 CPU 会达到 100% 并锁定系统。我希望我的引擎尽可能快地运行,同时仍处理 Windows 事件,以便在遇到类似于上述的紧密循环时其他应用程序仍能响应。

所以我的第一个问题是如何将代码添加到我的主循环中,以确保在不减慢应该以尽可能快的速度运行的主引擎的情况下处理 Windows 事件。

另外,如果能够设置我的引擎可以使用的最大 CPU 使用率,并通过调用偶尔的 Sleep(1) 来降低 CPU 使用率,那就太好了。

所以我的第二个问题是如何将 CPU 使用率降低到所需的水平?

引擎是用 Borland C++ 编写的,并调用 win32 API。

提前致谢

【问题讨论】:

  • 如果您正在制定一个协作多任务方案,而脚本不合作 - 您真的应该强制执行吗?如果这是您想要的,请建立一个抢占机制。
  • 恕我直言,你要倒退一步(ala windows 3.1)。您拥有完整的多进程和多线程操作系统。为什么不让每个脚本在它自己的线程中运行并管理这些线程。操作系统将负责执行上下文切换并为每个脚本分配处理器时间。然后,您可以控制线程优先级并管理线程生命周期。对于如图所示的无限循环,您可以在引擎上采取一些步骤来终止有问题的线程,即使您不这样做,这也永远不会阻止其他脚本运行。
  • 肖恩,你似乎是新来的:欢迎 :) 距离最后一个答案发布已经有几天了。您应该对您认为有用的答案进行投票(单击答案左上角数字上方的三角形),如果有任何答案很好地回答了您的问题,请将其标记为已接受的答案(单击勾号。)我们付出了很多努力在这里回答问题,很利他,但能得到积分的“奖励”也不错。

标签: windows multithreading winapi cpu-usage


【解决方案1】:

1。在运行脚本的同时运行消息循环

我希望我的引擎运行得尽可能快 可能,同时仍在处理 windows事件,使其他 应用程序仍然响应时 类似于上面的紧密循环是 遇到过。

在执行另一个操作时继续运行消息循环的最佳方法是将另一个操作移到另一个线程。换句话说,将您的脚本解释器移动到第二个线程并从您的主 UI 线程与其通信,该线程运行消息循环。

当您说 Borland C++ 时,我假设您使用的是 C++ Builder?在这种情况下,主线程是唯一与 UI 交互的线程,其消息循环通过Application->Run 运行。如果您在库回调中定期调用Application->ProcessMessages,那是可重入的,可能会导致问题。不要这样做。

对您的问题的一条评论建议将每个脚本实例移至单独的线程。这将是理想的。但是,请注意脚本调用的 DLL 如果它们保持状态会出现问题 - DLL 是按进程加载的,而不是按线程加载的,因此如果它们保持状态,您可能会遇到线程问题。目前纯粹为了解决您当前的问题,我建议您将所有脚本执行移至另一个线程。

您可以通过多种方式在线程之间进行通信,例如使用PostMessagePostThreadMessage 在它们之间发布消息。由于您使用的是 Borland C++,因此您应该可以访问 VCL。它有一个很好的线程包装类,叫做TThread。从中派生并将您的脚本循环放在执行中。您可以使用Synchronize(阻塞等待)或Queue(不阻塞;方法可以在目标线程处理其消息循环时随时运行)在另一个线程的上下文中运行方法。

附注:

这样其他 应用程序仍然响应时 类似于上面的紧密循环是 遇到过。

这很奇怪。在现代的、抢占式多任务版本的 Windows 中,即使您的程序非常繁忙,其他应用程序也应该仍然可以响应。您是否对线程优先级做了任何奇怪的事情,或者您是否使用了大量内存以使其他应用程序被换出?

2。处理脚本中的无限循环

你写:

没有什么可以阻止某人 编写一个脚本 像这样的紧密循环:

而(1) x=1;结束

这意味着我的主循环永远不会 进入睡眠状态等 CPU 上升到 100% 并锁定 系统。

但是如何处理这个问题:

如果能够 设置我的引擎的最大 CPU 使用率 可以使用和降低 CPU 通过调用偶尔使用 睡眠(1)..

所以我的第二个问题是我该怎么做 将 CPU 使用率降低到 要求等级?

我认为你采取了错误的方法。像while(1) x=1; endwhile 这样的无限循环是脚本中的一个错误,但它不应关闭您的主机应用程序。仅仅限制 CPU 不会使您的应用程序能够处理这种情况。 (而且使用大量 CPU 不一定是个问题:如果工作可供 CPU 运行,那就去做吧!只使用一点计算机的 CPU 并没有什么神圣的。它就在那里毕竟要使用。)(我认为)您真正想要的是能够继续让您的应用程序在运行此脚本时能够响应(由第二个线程解决),然后:

  1. 检测脚本何时“未响应”或未调用您的回调

  2. 能够采取行动,例如询问用户是否要终止脚本

另一个程序的例子是 Firefox。如果您转到一个包含异常脚本的页面,最终您会看到一个对话框,询问您是否要停止脚本运行。

如果不了解您的脚本实际是如何解释或运行的,我无法对这两个给出详细的答案。但我可以建议一种方法,即:

  1. 您的解释器可能会运行一个循环,获取下一条指令并执行它。您的交互性当前由从正在执行的这些指令之一运行的回调提供。我建议通过让您的回调简单地记录上次调用它的时间来利用它。然后在您的处理线程中,每条指令(或每十条或一百条)检查当前时间与上次回调时间。如果过了很长时间,比如十五或三十秒,这可能表明脚本被卡住了。通知主线程但继续处理。

    对于“时间”,GetTickCount 之类的内容可能就足够了。

  2. 下一步:您的主 UI 线程可以通过询问用户要做什么来对此做出反应。如果他们想终止脚本,请与脚本线程通信以设置标志。在您的脚本处理循环中,每条指令(或一百条)再次检查此标志,如果已设置,则停止。

    当您转移到每个脚本解释器有一个线程时,您TThreadTerminated 标记为此。对于在线程中无限运行的东西,惯用的做法是在 Execute 函数中的 while (!Terminated && [any other conditions]) 循环中运行。

要真正回答您关于使用更少 CPU 的问题,最好的方法可能是使用 SetThreadPriority 将线程的优先级更改为较低的优先级,例如 THREAD_PRIORITY_BELOW_NORMAL。如果没有其他需要运行,它仍然会运行。这将影响脚本的性能。另一种方法是按照您的建议使用Sleep,但这确实是人为的。也许SwitchToThread 稍微好一点——它让给操作系统选择的另一个线程。就个人而言,我认为 CPU 是可以使用的,如果您解决了交互式 UI 和处理失控脚本的问题,那么如果您的脚本需要,使用所有 CPU 应该没有问题。如果您使用“太多” CPU,也许解释器本身可以优化。您需要运行分析器并找出 CPU 时间用在了哪里。

【讨论】:

  • 感谢您的全面回复。我的解释器 DLL 在它自己的线程中运行,但如果该线程进入紧密循环,它将锁定 GUI 前端并减慢整个机器的速度。我认为答案是通过 Synchronize 调用 ProcessMessages 以确保仍然处理 Windows 事件。我没有在单独的线程中运行脚本的原因有很多,其中之一是可能有 1000 多个脚本同时运行,而且我不热衷于创建 5000 个操作系统线程的想法......跨度>
  • 您可以创建几个并让每个线程运行多个脚本实例。 ProcessMessages 一般不是个好主意,可以重入。如果你的主线程没有做任何其他事情,它应该已经在处理消息了!
【解决方案2】:

虽然设计糟糕的脚本可能会让您陷入无所事事的循环,但不要担心。 Windows 旨在处理此类事情,并且不会让您的程序占用超过其公平份额的 CPU。如果它确实能达到 100%,那只是因为没有其他东西想要运行。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-10-14
    • 1970-01-01
    • 1970-01-01
    • 2023-03-23
    • 1970-01-01
    • 1970-01-01
    • 2011-01-13
    相关资源
    最近更新 更多