什么是在线程中“浪费时间”的最高效CPU方式?

11

我有许多线程(数百个),每个线程每次执行几秒钟。 当它们正在执行时,它们花费大量时间等待来自另一个系统(串行设备)的响应。 我注意到同时执行100个线程可能是资源消耗过大,所以我实际上限制了可以同时启动的线程数。

我想到了在线程内等待外部事件的好方法和坏方法。这种方法会占用CPU吗?

send command ;
repeat
until response arrived ;
process response ;    

这种方法是否可以使其更有效率呢?:

send command ;
repeat
    Sleep (20) ;
until response arrived ;
process response ;  

* 附加信息 *

该环境为x86 Windows XP。线程代码与串口设备有很长的交互序列,但一般而言,它包括向COM端口写入字符(使用AsyncFree串行库)并等待字符通过在输入字符缓冲区上挂起并在到达时进行处理来返回。我想串行库会进行设备读取和写入。线程时间可能长达一分钟或短至几秒钟,但大部分时间都花费在等待字符离开端口或等待响应字符(波特率较慢),因此我提出了关于线程在等待时最佳行为方式的问题。目前,在等待CharactersInBuffer变为非零时,我在循环中调用Sleep,处理每个字符到达时,当我收到完整的响应时退出线程。所以代码看起来更像是这样的(忽略超时处理等):

send command ;
Packet = '' ;
repeat

    repeat
        Sleep (20) ;
    until response character arrived ;
    build Packet

until complete packet arrived
process response ;  

3
如果你看不到 CPU 利用率达到 100%,那么似乎你没有问题。不看实际代码很难说更多。"until response arrived" 是关键。它实际上是什么作用?它是一个阻塞调用吗? - David Heffernan
2
@rossmcm:应该有一种方法让线程等待来自操作系统的通知,表示响应已经就绪,而不是反复轮询。 - afrazier
@afrazier,请告诉这位发帖人。这就是我提问的原因。 - Marcus Adams
4
如果您的线程已经阻塞,那么您可能已经没有什么可做的了。 - David Heffernan
@Martin。最多有256个通道,每个通道都有自己的COM端口/FIFO等。所有256个通道都有可能同时触发一个线程(但目前我将其限制在5-10个左右)。所有通道都是独立的,不必等待彼此,尽管它们的OnTerminate事件可能会一起返回到主线程并被“串行”处理,但这种处理负载并不大。 - rossmcm
显示剩余8条评论
3个回答

7
如果线程真正地在等待像 WaitForSingleObject 这样不使用处理器时间的操作,然后超时,那么没有理由用 sleep 在线程中加入延迟。
您的用户并不需要等待线程响应,它不会使用处理器时间,其他线程也不会被阻塞,所以没有理由让线程休眠。
正如 David Heffernan 在他的评论中指出的那样,如果现在没有使用100% 的 CPU ,那么就没有问题。
如果您是单线程的,并且必须在等待串口响应之间偶尔响应用户,则可以使用 sleep()。
此外,使线程休眠不会使其更有效率。它只会将处理器周期让给其他线程。
看一下 sleep(0) 作为在线程中“浪费时间”的一种 CPU 高效方法。

此外,让线程休眠并不会使其更有效率。它只是将处理器周期让给其他线程而已。这不就是目的吗?如果我让我的线程睡眠20毫秒并将处理器让给其他线程,那么这不会为其他线程/进程提供更多的CPU吗?如果我有10000个线程,它们都刚刚进入了Sleep(10000)状态,那么它们的CPU使用率是多少? - rossmcm
@rossmom,如果我在车队中,停下卡车让其他人通过并不会增加效率。需要完成的工作量是相同的。这只是更有礼貌而已。如果没有人要让过,我停下卡车就是浪费时间。如果有10000个线程正在睡眠,从技术上讲,它们使用的CPU使用率为0,尽管操作系统(调度程序)线程将有很多工作要做,如果你的RAM用完了,你肯定会变慢。使用sleep(0)将尽快完成所有任务,同时仍然保持礼貌。Sleep(0)比sleep(200)更有效,因为时间也是一种资源。 - Marcus Adams
好的,我现在明白Sleep(0)实际上表示“继续执行,这里没有要做的事情”,所以如果我坚持当前的架构,限制可以启动的线程数量为十个,并将我的Sleep(20)替换为Sleep (0),我会得到一个低延迟和低CPU使用率的情况? - rossmcm
1
@rossmcm 如果没有等待运行的线程,Sleep(0) 将不会产生任何效果。否则它将会让出 CPU 时间。 - David Heffernan
@rossmcm,使用Sleep(0)代替Sleep(20),您将获得低延迟,但在有工作要做时保证更高的CPU使用率。 CPU使用率并不是坏事。如果没有其他人使用CPU,为什么不让您使用呢? Sleep(0)将使计算机对其他进程保持响应,这有助于保持UI的响应性,并允许用户在您的程序运行时继续观看电影或浏览网页。 如果您想使用Sleep(20)故意让用户等待更长时间,以便他们不会看到CPU峰值,那就是您的权利。 我更喜欢他们认为自己的计算机很慢而不是我的程序。 - Marcus Adams
Sleep(0) 不会让出 CPU 给其他应用程序,而是会让出给同一优先级的其他就绪线程(如果有的话)。如果您有 100 个带有 sleep(0) 循环且没有其他阻塞调用的线程,则会有 100 个就绪线程。如果另一个单线程应用程序正在运行并尝试取得进展,则其就绪线程将获得与您应用程序中的 100 个线程相同的 CPU,即可用 CPU 的 1/101。正如其他人发布的那样,这完全取决于检查可用字符是否会阻塞。如果是,则 sleep(任何值) 没有意义 - 只需将其注释掉即可。如果不是,则应该这样做! - Martin James

2
防止线程占用 CPU 时间的最有效方法是将其置于“等待模式”中。
我自己并不使用 Delphi,但似乎它的基础知识已经掌握了。请参见"第11章 同步器和事件",尤其是"使用信号量模拟事件"
如果您想要在不使用 CPU 的情况下等待,请使用WaitForEvent

检查事件的信号状态。如果它指示事件被发出信号,则会发出内部信号量,并将阻塞在信号量上的线程数减少。然后增加阻塞线程的计数,并对内部信号量进行等待。

如果这与I/O有关,则情况会略有不同。如果是套接字,则可能已经是阻塞的,如果是异步I/O,则可以使用信号量和WaitForEvent等方法。
在.NET中,有Monitor.WaitMonitor.SignalManualResetEventCountDownLatch等工具,但我不知道Delphi中的等效物是什么。

2
我无法评估AsyncFree的能力,但通常在Windows中的COM端口编程支持Overlapped I/O,因此您可以使用WaitCommEvent()函数和WaitFor...()函数族之一(例如WaitForSingleObject())有效地等待数据到达时的通知。线程可以进入睡眠状态,直到发出通知,在此时它会“唤醒”以从端口读取数据,直到没有更多数据可读,然后它可以再次进入睡眠状态,直到下一个通知。请保留HTML标签。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接