可以 c/c++在单线程中进行抢占式多任务处理吗?

发布于 2024-12-12 08:47:13 字数 852 浏览 0 评论 0原文

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

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

如果是这样,怎么办?

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


我问是因为我很好奇。

我多年来一直致力于制作异步 IO 循环。当编写异步 IO 循环时,我必须非常小心,不要将昂贵的计算放入循环中,因为它会破坏循环。

因此,我对异步 IO 循环恢复甚至完全支持某种绿色线程或此类方法的各种方法感兴趣。例如,对 SIGALRM 中的活动任务和循环迭代次数进行采样,然后如果检测到某个任务正在阻塞,则将所有其他任务移至新线程,或者进行一些狡猾的变体就达到了预期的结果。

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

Preemptive multitasking in C/C++: can a running thread be interrupted by some timer and switch between tasks?

Many VMs and other language runtimes using green-threading and such are implemented in these terms; can C/C++ apps do the same?

If so, how?

This is going to be platform dependent, so please discuss this in terms of the support particular platforms have for this; e.g. if there's some magic you can do in a SIGALRM handler on Linux to swap some kind of internal stack (perhaps using longjmp?), that'd be great!


I ask because I am curious.

I have been working for several years making async IO loops. When writing async IO loops I have to be very careful not to put expensive to compute computation into the loop as it will DOS the loop.

I therefore have an interest in the various ways an async IO loop can be made to recover or even fully support some kind of green threading or such approach. For example, sampling the active task and the number of loop iterations in a SIGALRM, and then if a task is detected to be blocking, move the whole of everything else to a new thread, or some cunning variation on this with the desired result.

There was some complaints about node.js in this regard recently, and elsewhere I've seen tantalizing comments about other runtimes such as Go and Haskell. But lets not go too far away from the basic question of whether you can do preemptive multitasking in a single thread in C/C++

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

旧夏天 2024-12-19 08:47:26

标题是一个矛盾的说法,线程是一个独立的执行路径,如果你有两个这样的路径,那么你就有多个线程。

你可以做一种“穷人的”使用 setjmp/longjmp 进行多任务处理,但我不推荐它,它是协作式的而不是抢占式的。

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

The title is an oxymoron, a thread is an independent execution path, if you have two such paths, you have more than one thread.

You can do a kind of "poor-man's" multitasking using setjmp/longjmp, but I would not recommend it and it is cooperative rather than pre-emptive.

Neither C nor C++ intrinsically support multi-threading, but there are numerous libraries for supporting it, such as native Win32 threads, pthreads (POSIX threads), boost threads, and frameworks such as Qt and WxWidgets have support for threads also.

凯凯我们等你回来 2024-12-19 08:47:25

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

你通常做的是这样的:

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

What you're asking makes no sense. What would your one thread be interrupted by? Any executing code has to be in a thread. And each thread is basically a sequential execution of code. For a thread to be interrupted, it has to be interrupted by something. You can't just jump around randomly inside your existing thread as a response to an interrupt. Then it's no longer a thread in the usual sense.

What you normally do is this:

  • either you have multiple threads, and one of your threads is suspended until the alarm is triggered,
  • alternatively, you have one thread, which runs in some kind of event loop, where it receives events from (among other sources) the OS. When the alarm is triggered, it sends a message to your thread's event loop. If your thread is busy doing something else, it won't immediately see this message, but once it gets back into the event loop and processing events, it'll get it, and react.
秉烛思 2024-12-19 08:47:24

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

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

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

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


关于最佳输入/输出调度

你可以看看Boost Asio:前摄器设计模式:无线程并发

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

示例 HTTP 服务器4是该技术的一个示例。

Boost Asio (Kohlhoff) 的作者在他的 此处博客:无堆栈协程的实用指南

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

As others have outlined, preemptive is likely not very easy to do.

The usual pattern for this is using co-procedures.

Coprocedures are a very nice way to express finite state machines (e.g. text parsers, communication handlers).

You can 'emulate' the syntax of co-procedures with a modicum of preprocessor macro magic.


Regarding optimal input/output scheduling

You could have a look at Boost Asio: The Proactor Design Pattern: Concurrency Without Threads

Asio also has a co-procedure 'emulation' model based on a single (IIRC) simple preprocessor macro, combined with some amount of cunningly designed template facilities that come things eerily close to compiler support for _stack-less co procedures.

The sample HTTP Server 4 is an example of the technique.

The author of Boost Asio (Kohlhoff) explains the mechanism and the sample on his Blog here: A potted guide to stackless coroutines

Be sure to look for the other posts in that series!

相权↑美人 2024-12-19 08:47:22

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

您可能可以从 SIGALARM 信号处理程序中使用 getcontext()/setcontext()...,但是如果它有效,那就会很混乱充其量。我不明白这种方法相对于内核线程或基于事件的 I/O 有什么优势:您可以获得抢占性的所有非确定性,并且您无需将程序分成顺序控制流。

Userspace threading libraries are usually cooperative (e.g: GNU pth, SGI's statethreads, ...). If you want preemptiveness, you'd go to kernel-level threading.

You could probably use getcontext()/setcontext()... from a SIGALARM signal handler, but if it works, it would be messy at best. I don't see what advantage has this approach over kernel threading or event-based I/O: you get all the non-determinism of preemptiveness, and you don't have your program separated into sequential control flows.

花开雨落又逢春i 2024-12-19 08:47:21

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

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

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

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

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

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

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

关于 setjmp/longjmp 您需要注意,它们在与 C++ 一起使用时容易出现几个问题。

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

有关一些参考资料,请参阅

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

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

备注:

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

As far as i understand you are mixing things that are usually not mixed:

  • Asynchrounous Singals
    A signal is usually delivered to the program (thus in your description one thread) on the same stack that is currently running and runs the registered signal handler... in BSD unix there is an option to let the handler run on a separate so-called "signal stack".

  • Threads and Stacks
    The ability to run a thread on its own stack requires the ability to allocate stack space and save and restore state information (that includes all registers...) - otherwise clean "context switch" between threads/processes etc. is impossible. Usually this is implemented in the kernel and very often using some form of assembler since that is a very low-level and very time-sensitive operation.

  • Scheduler
    AFAIK every system capable of running threads has some sort of scheduler... which is basically a piece of code running with the highest privileges. Often it has subscribed to some HW signal (clock or whatever) and makes sure that no other code ever registers directly (only indirectly) to that same signal. The scheduler has thus the ability to preemt anything on that system. Main conern is usually to give the threads enough CPU cycles on the available cores to do their job. Implementation usually includes some sort of queues (often more than one), priority handling and several other stuff. Kernel-side threads usually have a higher priority than anything else.

  • Modern CPUs
    On modern CPUs the implementation is rather complicated since involves dealing with several cores and even some "special threads" (i.e. hyperthreads)... since modern CPUs usually have several levels of Cache etc. it is very important to deal with these appropriately to achieve high performance.

All the above means that your thread can and most probably will be preempted by OS on a regular basis.

In C you can register signal handlers which in turn preempt your thread on the same stack... BEWARE that singal handlers are problematic if reentered... you can either put the processing into the signal handler or fill some structure (for example a queue) and have that queue content consumed by your thread...

Regarding setjmp/longjmp you need to be aware that they are prone to several problems when used with C++.

For Linux there is/was a "full preemption patch" available which allows you to tell the scheduler to run your thread(s) with even higher priority than kernel thread (disk I/O...) get!

For some references see

For seeing an acutal implementation of a scheduler etc. checkout the linux serouce code at https://kernel.org .

Since your question isn't very specific I am not sure whether this is a real answer but I suspect it has enough information to get you started.

REMARK:

I am not sure why you might want to implement something already present in the OS... if it for a higher performance on some async I/O then there are several options with maximum performance usually available on the kernel-level (i.e. write kernel-mode code)... perhaps you can clarify so that a more specific answer is possible.

面犯桃花 2024-12-19 08:47:19

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

Windows has fibers that are user-scheduled units of execution sharing the same thread.
http://msdn.microsoft.com/en-us/library/windows/desktop/ms682661%28v=vs.85%29.aspx

UPD: More information about user-scheduled context switching can be found in LuaJIT sources, it supports coroutines for different platforms, so looking at the sources can be useful even if you are not using lua at all. Here is the summary: http://coco.luajit.org/portability.html,

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文