9

C/C++ 中的抢占式多任务:正在运行的线程可以被某个计时器中断并在任务之间切换吗?

许多使用绿色线程等的虚拟机和其他语言运行时都是以这些术语实现的;C/C++ 应用程序可以做同样的事情吗?

如果是这样,怎么做?

这将取决于平台,因此请根据特定平台对此的支持进行讨论;例如,如果您可以SIGALRM在 Linux 上的处理程序中执行一些魔法来交换某种内部堆栈(可能使用longjmp?),那就太好了!


我问是因为我很好奇。

我已经工作了几年来制作异步 IO 循环。在编写异步 IO 循环时,我必须非常小心,不要将昂贵的计算计算放入循环中,因为它会 DOS 循环。

因此,我对可以使异步 IO 循环恢复甚至完全支持某种绿色线程或此类方法的各种方式感兴趣。例如,对 a 中的活动任务和循环迭代次数进行采样SIGALRM,然后如果检测到任务被阻塞,则将其他所有内容移至新线程,或者对此进行一些巧妙的变体以获得所需的结果。

最近在这方面有一些关于 node.js 的抱怨,在其他地方我看到了关于其他运行时(如 Go 和Haskell )的诱人评论。但是,让我们不要离是否可以在 C/C++ 中的单个线程中进行抢占式多任务处理的基本问题太远

4

6 回答 6

3

Windows 具有共享同一线程的用户计划执行单元的纤程。 http://msdn.microsoft.com/en-us/library/windows/desktop/ms682661%28v=vs.85%29.aspx

UPD:有关用户调度的上下文切换的更多信息可以在 LuaJIT 源代码中找到,它支持不同平台的协程,因此即使您根本不使用 lua,查看源代码也很有用。这是摘要:http://coco.luajit.org/portability.html

于 2011-10-30T08:42:56.900 回答
3

据我了解,您正在混合通常不混合的东西:

  • 异步
    信号通常将信号传递给当前正在运行并运行已注册信号处理程序的同一堆栈上的程序(因此在您的描述中为一个线程)...在 BSD unix 中有一个选项可以让处理程序在单独的所谓的“信号栈”。

  • 线程和堆栈
    在自己的堆栈上运行线程的能力需要能够分配堆栈空间并保存和恢复状态信息(包括所有寄存器......) - 否则线程/进程等之间的干净“上下文切换”是不可能的. 通常这是在内核中实现的,并且经常使用某种形式的汇编程序,因为这是一个非常低级且对时间非常敏感的操作。

  • 调度程序
    AFAIK 每个能够运行线程的系统都有某种调度程序......这基本上是一段以最高权限运行的代码。通常它订阅了一些硬件信号(时钟或其他),并确保没有其他代码直接(仅间接)注册到同一信号。因此,调度程序有能力抢占该系统上的任何内容。主要问题通常是在可用内核上为线程提供足够的 CPU 周期来完成它们的工作。实现通常包括某种队列(通常不止一个)、优先级处理和其他一些东西。内核端线程通常比其他任何线程具有更高的优先级。

  • 现代 CPU
    在现代 CPU 上,实现相当复杂,因为涉及处理多个内核甚至一些“特殊线程”(即超线程)......因为现代 CPU 通常具有多个级别的缓存等。适当地处理这些非常重要以实现高性能。

以上所有意味着您的线程可以并且很可能会定期被操作系统抢占。

在 C 中,您可以注册信号处理程序,从而在同一堆栈上抢占您的线程...请注意,如果重新输入信号处理程序会出现问题...您可以将处理放入信号处理程序或填充某些结构(例如队列) 并让您的线程使用该队列内容...

关于setjmp/longjmp你需要知道它们在与 C++ 一起使用时容易出现几个问题。

对于 Linux,有/曾经有一个“完全抢占补丁”可用,它允许您告诉调度程序以比内核线程(磁盘 I/O...)更高的优先级运行您的线程!

一些参考见

要查看调度程序等的实际实现,请查看https://kernel.org上的 linux 服务代码。

由于您的问题不是很具体,我不确定这是否是一个真正的答案,但我怀疑它有足够的信息让您开始。

评论:

我不确定你为什么要实现操作系统中已经存在的东西......如果它是为了在某些异步 I/O 上获得更高的性能,那么内核级别通常有几个具有最高性能的选项(即编写内核-模式代码)......也许你可以澄清一下,以便更具体的答案成为可能。

于 2011-10-30T10:27:54.953 回答
2

用户空间线程库通常是协作的(例如:GNU pth,SGI 的 statethreads,...)。如果你想要抢占式,你会去内核级线程。

您可能可以getcontext()/setcontext()...SIGALARM信号处理程序中使用,但如果它有效,它充其量只是一团糟。我看不出这种方法与内核线程或基于事件的 I/O 相比有什么优势:您获得了抢占性的所有不确定性,并且您没有将程序分成顺序控制流。

于 2011-10-30T10:14:29.453 回答
1

正如其他人所概述的那样,先发制人可能不太容易做到。

通常的模式是使用协同程序。

Coprocedures 是一种非常好的表达有限状态机(例如文本解析器、通信处理程序)的方式。

您可以使用少量的预处理器宏魔术来“模拟”协同过程的语法。


关于最佳输入/输出调度

你可以看看 Boost Asio:Proactor 设计模式:没有线程的并发

Asio 还具有基于单个 (IIRC) 简单预处理器宏的协同过程“仿真”模型,并结合了一些巧妙设计的模板工具,这些工具非常接近于编译器对 _stack-less 协同过程的支持。

示例HTTP Server 4是该技术的一个示例。

Boost Asio (Kohlhoff) 的作者在他的博客上解释了机制和示例:A potted guide to stackless coroutines

请务必查找该系列中的其他帖子!

于 2011-10-30T10:15:54.410 回答
0

你问的没有意义。你的一个线程会被什么打断?任何执行代码都必须在线程中。而且每个线程基本上都是代码的顺序执行。对于要中断的线程,它必须被某些东西中断。您不能只是在现有线程中随机跳来跳去作为对中断的响应。那么它就不再是通常意义上的线程了。

你通常做的是这样的:

  • 要么您有多个线程,并且您的一个线程被挂起,直到触发警报,
  • 或者,您有一个线程,它在某种事件循环中运行,它从(以及其他来源)操作系统接收事件。当警报被触发时,它会向您的线程的事件循环发送一条消息。如果您的线程正忙于做其他事情,它不会立即看到此消息,但是一旦它回到事件循环并处理事件,它就会得到它并做出反应。
于 2011-10-30T09:06:35.667 回答
0

标题是矛盾的,一个线程是一个独立的执行路径,如果你有两条这样的路径,你就有不止一个线程。

您可以使用 setjmp/longjmp 进行一种“穷人”的多任务处理,但我不推荐它,它是合作的而不是先发制人的。

C 和 C++ 本质上都不支持多线程,但有许多库支持它,例如本机 Win32 线程、pthread(POSIX 线程)、boost 线程,并且 Qt 和 WxWidgets 等框架也支持线程。

于 2011-10-30T10:26:52.993 回答