如何实现多个“线程”仅在一个线程中运行
最近我一直在想:他们如何在一个线程中实现多个“线程”?
我的意思是,他们如何在一个线程中实现多个并行运行的代码片段?他们如何保存“线程”的状态、创建中断并将 CPU 传递给下一个线程?
我认为 Scala-actor 实现了这一点。但如何呢?
这个对于JVM或者C都可以回答,没关系。我只是真的想学习它的理论。
Lately I've been thinking: How do they implement several 'threads' in only one thread?
I mean, how they implement several parallel running pieces of code in only one thread? How they save the state of the 'thread', create an interrupt and pass the CPU to the next one?
I think that the Scala-actors implement this. But how?
This can be answered for JVM or C, it doesn't matter. I just really want to learn the theory of it.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
我认为您混淆了 协程 和 绿色线程在这里。
协程在准备好执行操作时会放弃控制权,不会造成任何中断,因此有关中断的问题在这里无关紧要。 Scala actor 作为协程实现。
绿色线程是虚拟机实现的用户模式线程,不使用本机操作系统功能。显然,虚拟机可以在正在执行的代码中插入任何指令,以检查是否需要切换到另一个线程。
I think you are confusing coroutines and green threads here.
Coroutines give up the control when they are ready to do it, without any interruption, so question about interruption is irrelevant here. Scala actors are implemented as coroutines.
Green threads are user-mode threads implemented by virtual machine without use of native OS capabilities. Obviously, virtual machine can insert any instructions into the code being executed in order to check whether it need to switch to another thread.
对于 Actor,这很简单,您可以使用同一线程为多个 Actor 执行消息,而不是为每个 Actor 使用一个线程。然而,如果一个actor执行阻塞调用或繁重的计算,则必须使用另一个线程来执行其他actor中的消息。
绿色线程是可以在虚拟机级别实现的轻量级线程。绿色线程始终映射到一个或多个操作系统线程。同步和线程切换由VM在用户空间中处理,这可以显着减少开销。但是,绿色线程也有缺点,例如 IO 调用可能会导致线程阻塞,然后 VM 无法为另一个绿色线程“重用”操作系统线程,而必须使用额外的操作系统线程。
另一种解决方案是使用延续,如 Scala 编译器中实现的那样。然后,执行的中断和恢复在 JVM 字节码级别进行处理,其中保存和恢复本地状态。不需要虚拟机支持。
With actors it's simple, instead of using one thread per actor you use the same thread for executing messages for multiple actors. However, if an actor performs a blocking call or a heavy computation, another thread must be used to execute messages in other actors.
Green threads are light weight threads that can be implemented at the VM level. Green threads are always mapped to one or more OS threads. Synchronization and thread switching is handled in user space by the VM, which can significantly reduce overhead. However, there are drawbacks to green threads, for example IO calls might cause the thread to block and then the VM can't "reuse" the OS thread for another green thread and must instead use an additional OS thread.
Another solution is to use continuations, as implemented in the Scala compiler. The interruption and resuming of execution is then handled at the JVM bytecode level where local state is saved and restored. No VM support is needed.
您是指 Java 中 ExecutorService 或 ScheduledExecutorService 中的任务吗?
这些任务被添加到队列中并执行直至完成。当一个完成时,另一个开始。如果您有一个带有延迟的循环,则可以使用重复的计划任务。它完成每次迭代并允许其他任务运行。
如果您想了解更多详细信息,您可能会发现阅读代码很有趣。
Do you mean like tasks in an ExecutorService or ScheduledExecutorService in Java?
These tasks are added to a queue and preformed to completion. When one finishes another one starts. If you have a loop with a delay, you can use a repeating scheduled tasks instead. It complete for each iteration and allows another tasks to run.
If you want to know more details you might find reading the code interesting.
使用 协程
Use coroutines
Akka 库 是 Actor 模型的一个非常好的实现。它有一个非常好的直接 Java API(除了 Scala 之外),以及 该文档非常好。
The Akka library is a really nice implementation of the actors model. It's got a pretty good straight Java API (in addition to the Scala one), and the doc is pretty good.
实现此目的的一种方法是让用户代码中的线程包自行注册以获取来自内核的某种计时器中断。每当它收到这样的中断时,它就可以告诉内核停止执行本身运行多个不同线程的所有内核线程。对于每个线程,定时器中断代码可以检查这些线程的堆栈,在辅助位置记录重要信息(寄存器、堆栈指针、程序计数器等),然后加载另一个模拟线程的存储信息。在该实际线程上运行的线程。然后它可以恢复运行模拟线程的内核线程。通过这种方式,您可以模拟在单个内核线程上运行的多个线程之间的上下文切换。
要实现锁定之类的功能,您可以在用户空间本地跟踪所有锁定信息。每当模拟线程尝试获取锁时,您都可以检查该线程是否能够成功获取锁。如果是这样,你只需给它锁即可。否则,您可以通过交换在真实线程上运行的模拟线程来模拟上下文切换,然后将模拟线程标记为阻塞,直到锁再次释放。
这只是一个开始 - 这里还有很多其他细节(如果其中一个模拟线程尝试执行阻塞 I/O 操作怎么办?您不能只阻塞内核线程,因为这会停止所有模拟线程! ),但这就是这个想法的要点。
One way to do this is to have the threading package in the user code register itself for some sort of timer interrupt from the kernel. Whenever it receives such an interrupt, it can tell the kernel to halt execution of all of the kernel threads that are themselves running multiple different threads. For each of those threads, the timer interrupt code can inspect the stack for those threads, record important information (registers, stack pointer, program counter, etc.) in an auxiliary location, then load in the stored information for another one of the simulated threads running on that actual thread. It can then resume the kernel thread running the simulated thread. In this way, you can simulate context switching between the multiple threads running on a single kernel thread.
To implement something like locking, you could keep track of all the lock information locally in your user space. Whenever a simulated thread tries to acquire a lock, you can check whether the thread can successfully get the lock. If so, you just give it the lock. Otherwise, you simulate a context switch by swapping out what simulated thread is running on that real thread, then marking the simulated thread as blocked until the lock becomes free again.
This is just a start - there's a lot of other details here (what if one of the simulated threads tries to do a blocking I/O operation? You can't just block the kernel thread, since that would halt all the simulated threads!), but this is the gist of the idea.