“忙碌等待”与“睡眠”的权衡是啥?
Posted
技术标签:
【中文标题】“忙碌等待”与“睡眠”的权衡是啥?【英文标题】:What are trade offs for "busy wait" vs "sleep"?“忙碌等待”与“睡眠”的权衡是什么? 【发布时间】:2010-11-09 14:32:12 【问题描述】:这是对我之前问题的扩展
How does blocking mode in unix/linux sockets works?
我现在从 Internet 收集到的信息,所有调用阻塞调用的进程都处于休眠状态,直到调度程序找到解除阻塞的原因。原因可能会有所不同,从缓冲区空到缓冲区满再到任何其他情况。
但是,这是否是一种有效的实时方式,比如说硬/固定实时应用程序?因为当解锁条件为真时,进程并没有被解锁,而是当调度器给他他的 CPU 片时,并且解锁条件都为真。
好像你想要一个响应式解决方案,我不认为“自旋锁”或“忙等待”是正确的方法,CPU 片被浪费,并且整个系统将变得无响应或可能反应迟钝。
有人可以清除这些相互矛盾的想法吗?
【问题讨论】:
【参考方案1】:在调度程序唤醒您之前进入睡眠状态是正常/首选的做法。
旋转(不睡觉的另一种等待方式)不太常见,并具有以下效果:
保持 CPU 忙碌,并阻止其他线程使用 CPU(直到/除非旋转线程完成其时间片并被抢占)
可以在您等待的事情发生的那一刻停止旋转(因为您一直在检查该事件,并且您不需要花时间被唤醒,因为你已经醒了)
不调用进入睡眠和再次唤醒所需的 CPU 指令
如果延迟的长度非常短(例如,如果延迟只需要执行 100 CPU 指令)。
【讨论】:
【参考方案2】:自旋锁会烧毁 CPU 和轮询的资源路径,以便在未发生所需事件的情况下继续浪费资源。
阻塞操作最重要的不同之处在于将 CPU 和相关资源路径排除在外,并在预期发生所需事件的资源上安装某种形式的 wait
。
在多任务或多线程/处理器环境中(很长一段时间以来的常见情况),在所需事件尚未到达时可能进行其他操作,烧毁 CPU 和资源访问路径会导致处理能力的严重浪费和时间。
当我们有一个超线程系统时(就像我认为您在问题中所指的那样),重要的是要注意 CPU 线程被切片的粒度非常高。我会伸出我的脖子来观察所有的事件——你倾向于阻塞——都需要足够的时间才能出现,以补偿他们在解除阻塞之前必须额外等待的小时间片。
我认为J-16
s 的观点是针对休眠(阻塞)线程在阻塞状态下未使用其代码和数据空间的情况。这可能会使系统放弃资源(如数据/代码缓存),然后在释放块时需要重新填充这些资源。因此,根据条件,一个块可能会造成更多的资源浪费。
这也是一个有效的注释,应该在设计和实现中进行检查。
但是,在大多数情况下,阻塞通常比自旋锁更好。
【讨论】:
对,我想我会提出一个答案,看看人们是否同意,否则我错了。不过谢谢。是的,j16 带来了不同的视角。这很好。并感谢 nik 强调它。 :)【参考方案3】:如果在您的应用程序的用例中,上下文切换会比消耗几个 CPU 周期更昂贵,因为您的条件可以保证在短时间内得到满足 时间,那么忙碌的等待可能对你有好处。
否则,您可以通过休眠或cond_wait()
ing 来强制放弃 CPU。
我能想到的强制上下文切换的另一种场景如下:
while(condition)
sleep(0);
【讨论】:
【参考方案4】:首先,你有一个误解:
阻塞调用不是“忙等待”或“自旋锁”。阻塞调用是可休眠的——这意味着 CPU 可以处理其他任务,没有 CPU 被浪费。
关于屏蔽通话的问题
阻塞调用更容易——它们更容易理解、更容易开发、更容易调试。
但他们是资源猪。如果你不使用线程,它会阻塞其他客户端;如果使用线程,每个线程都会占用内存和其他系统资源。即使你有足够的内存,切换线程也会使缓存变冷并降低性能。
这是一种权衡——更快的开发和可维护性?或可扩展性。
【讨论】:
@j-16,这正是我所说的,“阻塞呼叫是可睡眠的”请重新阅读问题。 阻塞调用怎么会占用资源,如果它们正在休眠,它们不会占用任何 CPU。 @Vivek:这里的“资源”主要是内存 @j-16,这里的每个人都在谈论 CPU 周期,因此我认为资源(尽管主观上)显然是 CPU。而且,据我所知,线程切换与进程的上下文切换不同。好吧,这取决于线程如何与内核的 LWP 绑定,但这是另一个特定于实现的主题。哦,好的,您的意思是在您的回答中您将资源称为内存。 @vivek:切换线程不会切换整个内存空间,但会切换寄存器和堆栈——这是上下文切换。即使您忽略内存使用量(这可能很重要),更改堆栈和重新填充缓存也会消耗 CPU 时间。【参考方案5】:我会尽量做到这一点,因为这里通过其他答案提供了足够的解释,是的,从所有这些答案中学习,我认为应该是一个完整的画面。 ---
我认为应该在系统的响应性与吞吐量之间进行权衡。
响应性 - 可以从两个角度考虑
在所有系统响应能力方面,以及 特定或每个进程的响应能力我认为对于系统的响应能力,阻塞调用是最好的方法。当阻塞调用处于阻塞状态时,它将 CPU 分配给就绪队列中的其他进程。
当然,对于特定进程或每个进程的响应性,我们将考虑忙碌等待/自旋锁模型。
现在,为了提高整体系统响应能力,我们不能减少调度程序的时间片(细粒度),因为这会在上下文切换中浪费过多的 CPU 资源。因此系统的吞吐量会急剧下降。当然,阻塞模型显然增加了系统的吞吐量,因为阻塞调用不会消耗 CPU 片,而是将其交给就绪队列中的其他/下一个进程。
我认为最好的做法是——设计一个考虑每个进程响应能力的系统,而不影响整体响应能力和吞吐量—— 通过实现基于优先级的调度程序,并考虑优先级反转问题,如果增加复杂性不会打扰您:)。
【讨论】:
想补充一点:我希望您能正确权衡自旋锁资源利用效率低下与阻塞模式(通常是细粒度,在硬件中)调度时间片的有效延迟。 是的,尼克,你说得对,整个辩论都围绕着这个权衡。【参考方案6】://改编的ASPI原源码...
DWORD startStopUnit (HANDLE handle, BOOL bLoEj, BOOL bStart)
DWORD dwStatus;
HANDLE heventSRB;
SRB_ExecSCSICmd s;
//here
heventSRB = CreateEvent (NULL, TRUE, FALSE, NULL);
memset (&s, 0, sizeof (s));
s.SRB_Cmd = SC_EXEC_SCSI_CMD;
s.SRB_HaID = 0;
s.SRB_Target = 0;
s.SRB_Lun = 0;
s.SRB_Flags = SRB_EVENT_NOTIFY;
s.SRB_SenseLen = SENSE_LEN;
s.SRB_CDBLen = 6;
s.SRB_PostProc = (LPVOID) heventSRB;
s.CDBByte[0] = 0x1B;
s.CDBByte[4] |= bLoEj ? 0x02 : 0x00;
s.CDBByte[4] |= bStart ? 0x01 : 0x00;
ResetEvent (heventSRB);
dwStatus = SPTISendASPI32Command (handle,(LPSRB) & s);
if (dwStatus == SS_PENDING)
//and here, don´t know a better way to wait for something to finish without processor cicles
WaitForSingleObject (heventSRB, DEFWAITLEN);
CloseHandle (heventSRB);
if (s.SRB_Status != SS_COMP)
printf("Erro\n");
return SS_ERR;
printf("nao Erro\n");
return s.SRB_Status;
【讨论】:
以上是关于“忙碌等待”与“睡眠”的权衡是啥?的主要内容,如果未能解决你的问题,请参考以下文章