背景:我正在使用Beej的指南,他提到了分叉并确保你“得到了僵尸”。我抓住的操作系统书籍解释了操作系统如何创建“线程”(我一直认为这是一个更基本的部分),引用它,我的意思是操作系统决定几乎所有东西。基本上他们共享所有外部资源,但他们分开了寄存器和堆栈空间(我认为第三件事)。
所以我得到了http://www.qnx.com的开发人员文档非常好解释的waitpid函数。事实上,我阅读了关于线程的整个部分,减去了进程和线程谷歌之后的所有类型的条件。
我可以将代码拆分并重新组合在一起并不会让我感到困惑。 如何我能做到这一点令人困惑。
在C和C ++中,你的程序是一个Main()函数,它继续前进,调用其他函数,可能永远循环(等待输入或渲染),然后最终退出或返回。在这个模型中,我认为没有理由让它超越“我正在等待某事”,在这种情况下它只是循环。
好吧,它似乎可以通过设置某些东西来循环,例如“我正在等待信号量”或“响应”或“中断”。或者它可能会在没有等待的情况下中断。这让我很困惑。
处理器时间片过程和线程。这一切都很好,花花公子,但它如何决定何时停止?我知道你进入轮询功能并说“嘿,我正在等待输入,时钟滴答或用户做某事”。不知怎的,它告诉了这个操作系统?我不确定。但是更多:
它似乎能够完全随机中断或插入,即使在单线程应用程序上也是如此。所以你正在运行一个线程并且突然waitpid()说“嘿,我完成了一个过程,让我打断这个,我们都讨厌僵尸,我必须这样做。”你还在循环一些计算。那么,刚刚发生了什么?我不知道,不知怎的,它们都运行了,你的计算没有被搞乱,因为它是单线程的,但是这并不意味着它不会停止它在同一个线程WHILE中运行waitpid()所做的事情你还在做其他应用程序。
同样令人困惑的是,如何通知你,比如iOS通知,并说“嘿,我得到了一些用户界面更改,让我从16岁开始,让我重新回到1,这样我就可以改变这一点”。但与上一段相同的问题,它如何中断正在运行的线程?
我想我理解分裂,但这种加入完全令人困惑。这就像教科书中有这个“帽子兔子”的步骤我应该接受。其他SO帖子告诉我他们不共享相同的堆栈,但这没有帮助,现在我想象一个紧身(堆栈)倾向于另一个紧身,但不确定它如何重组更改数据。
感谢您的帮助,我很抱歉这很长,但我知道如果我在这里过于简洁,我会误解这个并给我“他们是不同的堆栈”答案。
谢谢,
答案 0 :(得分:2)
好的,我会有一个去,虽然它会带来真实的经验' :)
它有点像这样:
OS内核调度程序/调度程序是用于管理线程的状态机。一个线程包含一个堆栈(在创建线程时分配),以及一个内核中的线程控制块(TCB)结构,它保存线程状态并可以存储线程上下文(包括用户寄存器,尤其是堆栈指针) )。线程必须有代码才能运行,但代码并不专用于线程 - 许多线程可以运行相同的代码。线程有状态,例如。在I / O上阻塞,在线程间信号上被阻塞,在一段时间内休眠,准备就绪,在核心上运行。
线程属于进程 - 进程必须至少有一个线程来运行其代码,并且在进程启动时由OS加载程序创建一个线程。主要线程'然后可以创建也属于该过程的其他人。
状态机输入是软件中断 - 来自那些已在内核上运行的线程的系统调用,以及来自perhiperal设备/控制器(磁盘,网络,鼠标,KB等)的硬件中断,它们使用处理器硬件功能来实现停止处理器运行来自线程的指令并立即'改为运行驱动程序代码。
状态机的输出是在核心上运行的一组线程。如果准备好的线程数少于核心,则操作系统将停止不可用的核心。如果有比线程更准备好的线程(即机器过载),则调度算法'决定使用线程运行时考虑了几个因素 - 线程和进程优先级,刚刚在I / O完成或线程间信号,前台进程增强等方面做好准备的线程的prority提升。
操作系统能够阻止任何核心上的任何正在运行的线程。它有一个处理器间硬件中断通道和驱动程序,可以强制任何线程进入操作系统并被阻止/停止,(可能因为另一个线程已经准备就绪,并且操作系统调度算法已决定必须立即抢占正在运行的线程)
运行线程的软件中断可以通过请求I / O或通过发信号通知其他线程(事件,互斥锁,条件变量和信号量)来更改正在运行的线程集。来自外围设备的硬件中断可以通过发出I / O完成信号来更改正在运行的线程集。
当OS获取这些输入时,它使用该输入和线程控制块和进程控制块结构的容器中的内部状态来决定下一个要运行的准备线程集。它可以通过在其TCB中保存其上下文(包括寄存器,尤其是堆栈指针)来阻止线程运行,而不是从中断返回。它可以通过将其上下文从TCB恢复到核心并执行中断返回来运行被阻塞的线程,从而允许线程从它停止的地方恢复。
增益是没有等待I / O的线程完全运行,因此不使用任何CPU,并且当I / O变为可用时,等待线程就会立即准备好'立即&#39 ;并且,如果有可用的核心,则运行。
这种操作系统状态数据和硬件/软件中断的组合,可以有效地匹配线程,这些线程可以使用可运行它们的核心向前推进,并且在轮询I / O或线程间通信标志时不会浪费CPU。 / p>
所有这些复杂性,无论是在操作系统还是必须设计多线程应用程序并因此忍受锁,同步,互斥等的开发人员,都只有一个重要目标 - 高性能I / O.如果没有它,你可以忘记视频流,BitTorrent和浏览器 - 它们都太可能无法使用了。
诸如“CPU量子”之类的语句和短语,放弃了剩余的时间片'和'循环赛'让我想要呕吐。
它是一台状态机。硬件和软件中断进入,出现了一组正在运行的线程。硬件定时器中断(可以超时系统调用,允许线程休眠并在超载的盒子上共享CPU)虽然很有价值,但它只是其中之一。
所以我在线程16上,我需要进入线程1来修改UI。一世 在任何地方随机停止,"将堆栈移到线程1"然后 "采取其背景并对其进行修改"?
不,时间与经济相关是经济的' #2 ......
线程1正在运行GUI。要做到这一点,它需要鼠标,键盘的输入。这种情况的典型方式是线程1在GUI输入队列(一个线程安全的生产者 - 消费者队列)上等待,阻止,用于KB /鼠标消息。它没有使用CPU - 核心是关闭运行服务和BitTorrent下载。您点击了键盘上的一个键,键盘控制器硬件在中断控制器上引发了一个中断线,导致核心在完成当前指令后立即跳转到键盘驱动程序代码。驱动程序读取KB控制器,组装一个KeyPressed消息并将其推送到具有焦点的GUI线程的输入队列 - 您的线程1.驱动程序通过调用调度程序中断入口点退出,以便可以执行调度运行和GUI线程被分配了一个核心运行它。要线程1,它所做的只是阻止' pop'调用一个队列,最后,它返回一个要处理的消息。
所以,线程1正在执行:
void* HandleGui{
while(true){
GUImessage message=thread1InputQueue.pop();
switch(message.type){
.. // lots of case statements to handle all the possible GUI messages
..
..
};
};
};
如果线程16想要与GUI交互,则无法直接执行。它所能做的就是以与KB /鼠标驱动程序类似的方式将消息排队到线程1,以指示它执行操作。
这似乎有点限制,但是来自线程16的消息可以包含多于POD。它可能有一个' RunMyCode'消息类型并包含一个函数指针,指向线程16希望在线程1的上下文中运行的代码。当线程1绕过消息时,它的' RunMyCode' case语句调用消息中的函数指针。请注意,这个简单的'机制是异步的 - 线程16发出了消息并运行 - 它不知道线程1什么时候会运行它传递的函数。如果函数访问线程16中的任何数据,这可能是一个问题 - 线程16也可能正在访问它。如果这是一个问题,(并且它可能不是 - 函数所需的所有数据都可能在消息中,当线程1调用它时可以作为参数传递给函数),可以使函数成为函数通过使线程16等待直到线程1运行该函数来调用同步。一种方法是将功能信号作为OS同步对象作为其最后一行 - 一个对象,线程16在排队其“运行代码”之后立即等待。消息:
void* runOnGUI(GUImessage message){
// do stuff with GUI controls
message.notifyCompletion->signal(); // tell thread 16 to run again
};
void* thread16run(){
..
..
GUImessage message;
waitEvent OSkernelWaitObject;
message.type=RunMyCode;
message.function=runOnGUI;
message.notifyCompletion=waitEvent;
thread1InputQueue.push(message); // ask thread 1 to run my function.
waitEvent->wait(); // wait, blocked, until the function is done
..
..
};
因此,让一个函数在另一个线程的上下文中运行需要合作。线程不能调用其他线程 - 只能通过操作系统发出信号。任何预期会运行的线程都会在外部发出信号'代码必须具有可以放置函数的可访问入口点,并且必须执行代码以检索函数地址并调用它。