我知道线程实际上并不能在同一个核心上并行运行,但在常规桌面系统中通常有数百甚至数千个线程。这当然远远超过今天4核心CPU的平均值。所以系统实际运行一些线程X时间,然后切换到另一个线程运行Y时间等等。
我的问题是,系统如何决定执行每个线程的时间?
我知道当一个程序在一个线程上调用sleep()
一段时间后,操作系统可以利用这段时间来执行其他线程,但是当程序根本不调用sleep时会发生什么?
E.g:
int main(int argc, char const *argv[])
{
while(true)
printf("busy");
return 0;
}
操作系统什么时候决定暂停此线程并激怒另一个?
答案 0 :(得分:2)
这取决于您的操作系统使用哪种类型的计划,例如让我们
为了公平地调度进程,循环调度程序通常采用时间共享,为每个作业提供一个时隙或量子(它允许CPU时间),如果当时没有完成,则中断作业。下次为该进程分配时隙时,将恢复作业。如果进程在其属性时间量程期间终止或将其状态更改为等待,则调度程序将选择就绪队列中的第一个进程来执行。
还有其他调度算法,您会发现此链接很有用:https://www.cs.uic.edu/~jbell/CourseNotes/OperatingSystems/5_CPU_Scheduling.html
答案 1 :(得分:2)
操作系统有一个名为scheduler的组件,它决定运行哪个线程以及运行多长时间。基本上有两种基本类型的调度程序:cooperative和preemptive。协作调度要求线程协作并定期将控制权交还给操作系统,例如通过执行某种IO。大多数现代操作系统都使用抢占式调度。
在抢占式调度中,操作系统为线程运行提供时间片。操作系统通过为CPU计时器设置处理程序来完成此操作:CPU定期运行一段代码(调度程序),检查当前线程的时间片是否结束,并可能决定将下一个时间片提供给一个等待运行的线程。时间片的大小以及如何选择下一个线程取决于您使用的操作系统和调度算法。当OS切换到新线程时,它将当前线程的CPU状态(寄存器内容,程序计数器等)保存到主存储器中,并恢复新线程的状态 - 这称为context switch。
如果你想了解更多,那么关于Scheduling的维基百科文章有很多信息和相关主题的指示。
答案 2 :(得分:1)
操作系统保留所有可以使用CPU执行的线程的容器(通常这些线程被描述为“已经”)。在大多数桌面系统上,这只是线程总数的一小部分。这类系统中的大多数线程都在等待I / O(这包括休眠 - 等待定时器I / O)或线程间信令;这样的线程不能使用CPU执行,因此操作系统不会将它们分配到核心上。
软件系统调用(例如,打开文件的请求,睡眠请求或等待来自另一个线程的信号),或来自外围设备的硬件中断(例如,磁盘控制器,NIC,KB) ,鼠标),可能会导致准备好的线程集发生变化,从而启动调度运行。
运行时,shceduler决定将哪些就绪线程分配给可用内核。它使用的算法是一种折衷方案,它试图通过平衡昂贵的上下文切换需求和响应I / O的需求来优化整体性能。内核可以阻止任何核心上的任何线程抢占它,但肯定不会这样做:)
所以:
我的问题是,系统如何决定执行的时间 每个帖子?
基本上,它没有。如果就绪线程集不大于内核数量,则无需停止/控制/影响CPU密集型循环 - 可以允许它永远运行,占用整个内核。
请注意,您的示例非常差 - printf()调用将请求来自操作系统的输出,如果不是立即可用,操作系统将阻止您看似“仅CPU”的线程,直到它为止。
但是当程序根本没有调用睡眠时会发生什么?
这只是另一个主题。如果它纯粹是CPU密集型的,那么它是否持续运行取决于盒子上的负载和可用的核心数量,如已经描述的那样。当然,它可以通过请求I / O或选择等待来自另一个线程的信号来阻止,从而将自己从准备好的线程集中移除。
请注意,一个I / O设备是硬件计时器。这对于超时系统调用和提供Sleep()功能非常有用。它通常会对那些准备好的线程数大于可用于运行它们的核心数量的盒子产生副作用(即盒子过载或运行的任务对CPU使用没有限制) 。它可以导致在就绪线程周围共享可用内核,因此给出了运行比实际物理能力更多线程的错觉,(尽量不要挂起Sleep()和定时器中断 - 这是许多中断之一)这可以改变线程状态。)
定时器硬件,中断和驱动程序的这种行为导致了“量子”,“分时”,“循环”等等。围绕现代抢占式内核操作的混乱和FUD。
抢先内核,它的驱动程序等,是一个状态机。来自外围设备的运行线程和硬件中断的Syscalls进入,出现了一组正在运行的线程。