所以我现在正在学习线程,我想知道一些事情是如何处理的。例如,假设我有一个程序,其中一个线程侦听输入,另一个线程在单个处理器上执行一些计算。当计算线程运行时,如果用户按下输入线程的按钮会发生什么?在切换到特定线程之前,输入线程不会忽略输入吗?
5 回答
这很大程度上取决于输入机制是如何实现的。实现 I/O 的一种简单但非常不优雅的方法是连续轮询......在这种情况下,输入线程可能处于循环中,一遍又一遍地读取硬件寄存器,并且当寄存器中的值发生变化时从 0 到 1,输入线程会知道按钮被按下:
void inputThread()
{
while(1)
{
if (some_register_indicates_the_button_is_pressed()) react();
}
}
这种方法的问题在于它的效率非常低——输入线程使用了数十亿个 CPU 周期,只是一遍又一遍地检查寄存器。在运行此代码的多线程系统中,线程调度程序将在每个时间段(例如每 10 毫秒一次)在忙等待的输入线程和计算线程之间切换 CPU,因此输入线程将使用一半的 CPU 周期和计算线程会用另一半。在这个系统中,如果输入线程在用户按下按钮的那一刻正在运行,那么输入几乎会在瞬间被检测到,但是如果计算线程正在运行,那么直到下一次输入线程得到时才会检测到输入运行,所以可能会有多达 10 毫秒的延迟。(更糟糕的是,如果用户过早释放按钮,
对连续轮询的改进是调度轮询。它的工作原理与上面相同,只是输入线程不是在循环中轮询,而是轮询一次,然后休眠一会儿,然后再次轮询:
void inputThread()
{
while(1)
{
if (some_register_indicates_the_button_is_pressed()) react();
usleep(3000); // sleep for 30 milliseconds
}
}
这与第一种情况相比效率要低得多,因为每次调用 usleep() 时,线程调度程序都会将输入线程置于睡眠状态,并且 CPU 立即可供任何其他线程使用。usleep() 还设置了一个硬件计时器,当该硬件计时器关闭时(30 毫秒后),它会引发一个中断。中断导致 CPU 停止它正在做的任何事情并再次运行线程调度代码,并且线程调度代码将(在大多数情况下)意识到 usleep() 返回的时间,并唤醒输入线程因此它可以对其循环进行另一次迭代。这仍然不是完美的:inputThread 仍在持续使用少量 CPU —— 不多,但如果你做很多这样的实例,它就会开始累加起来。还,
这导致我们使用中断驱动的 I/O。在这个模型中,输入线程根本不轮询;相反,它告诉操作系统在按下按钮时通知它:
void inputThread()
{
while(1)
{
sleep_until_button_is_pressed();
react();
}
}
反过来,操作系统的通知工具必须进行设置,以便在按下按钮时通知操作系统,以便操作系统可以唤醒并通知输入线程。操作系统通过告诉按钮的控制硬件在按钮被按下时产生中断来做到这一点。一旦该中断关闭,它的工作方式与前面示例中的定时器中断很相似;CPU 运行线程调度程序代码,它发现是时候唤醒输入线程,并让输入线程运行。这种机制具有非常好的特性:(1)输入线程在按下按钮时尽快被唤醒(没有等待计算线程首先完成其量子),以及(2)输入线程不会吃光任何 CPU 周期,除非按下按钮。由于这些优点,它'
请注意,在现代 PC 或 Mac 上,不仅仅是两个线程和一个硬件按钮;例如,有几十个硬件设备(键盘、鼠标、显卡、硬盘、网卡、声卡等)和几十个程序同时运行,操作系统的工作是在必要时在它们之间进行调解。尽管如此,一般原则还是一样的。假设在您的示例中,用户单击的按钮不是物理按钮,而是屏幕上的 GUI 按钮。在这种情况下,会发生以下事件序列:
- 用户的手指按下鼠标左键
- 鼠标的内部硬件通过 USB 电缆向计算机的 USB 控制器发送鼠标按钮按下消息
- 计算机的 USB 控制器产生中断
- 中断导致 CPU 中断计算线程的代码并运行 OS 的调度程序例程
- 线程调度程序看到 USB 中断线指示 USB 事件已准备好,并通过运行 USB 驱动程序的中断处理程序代码来响应
- USB 驱动程序的中断处理程序代码读入事件,发现它是鼠标按钮按下事件,并将其传递给窗口管理器
- 窗口管理器知道哪个窗口具有焦点,因此它知道将鼠标按钮按下事件转发给哪个程序
- 窗口管理器告诉操作系统唤醒与该窗口关联的输入线程
- 您的输入线程唤醒并调用 react()
如果您在单处理器系统上运行,那么可以。
嗯,线程可以通过多种方式相互通信。其中之一是具有全局变量并将其用作线程之间通信的缓冲区。
当您询问按钮时,必须有一个包含事件加载器循环的线程。根据我的经验,在这个线程中,输入不会被忽略。
你可以看到我关于这个主题的一些线程:
在这里,我对如何制作通过事件进行通信的 3 线程应用程序感兴趣。
简短的回答:是的,线程总是交互的。当它们以不可预测的方式交互时,问题开始出现。进程中的每个线程都可以访问整个进程内存空间,因此在一个线程中更改内存可能会破坏另一个线程的数据。
等待用户输入的线程将“立即”准备好。在大多数操作系统上,等待 I/O 并准备就绪的线程会获得临时优先级提升,即使在单核 CPU 上,也会“立即”抢占另一个以相同优先级运行的线程。
因此,如果一个单核 CPU 正在运行计算并且另一个等待的相同优先级的线程得到输入,它可能会立即运行。