在JavaScript中模拟上下文切换?

7
我一直在使用JavaScript实现一个相当复杂的系统,需要模拟多线程进程等内容。在真正的多线程进程(例如内核线程)中,可以通过上下文切换在不同线程之间进行切换。这是因为您可以将当前进程的程序计数器和寄存器存储到临时结构中,恢复其他进程的程序计数器和寄存器,然后从上一个进程离开的地方继续执行。
我想知道是否可能在JavaScript中实现类似的功能。目前我不知道如何做到这一点,因此一直在使用协作式多任务处理设计该系统。具体而言,我想要在多线程模拟器中运行的任何“函数”都被分成一个函数数组。为了执行“函数”,我遍历函数数组,按顺序执行每个函数,并维护一个“程序计数器”,指示下一个要执行的函数。这样,我就可以通过调用数组中的某个函数来模拟上下文切换,等待函数返回,然后切换到需要执行的其他函数数组。
我的当前方法可行,但在此系统中编写代码很困难。每个函数必须明确指出何时可以中断,并且因为数组中的函数都是独立的,所以在不同部分之间传递数据的逻辑很复杂。我希望能够更接近抢占式多任务处理。
我的问题是:是否可能以一种方式运行任意JavaScript函数,使其可以被外部源挂起和恢复?

3
每当有人在同一句话中使用JavaScript和多线程时,必须强制提及Web Workers。 - Yi Jiang
2个回答

4

1
stratifiedjs.org说:“StratifiedJS通过少量的关键词扩展了JavaScript语言,用于并发编程。它允许您以直观、结构化的顺序方式表达异步控制流。”听起来像是你需要的。 - tomg
@qwertymk- 这看起来很不错,但不幸的是(据我所知),它并没有被任何网络浏览器支持,这有些不符合初衷。不过这是一个很好的链接,我感谢你分享! - templatetypedef
@templatetypedef 它被Web浏览器支持。前往它们的控制台并尝试运行以下代码:console.log('等待3秒....'); hold(3000); console.log('完成') - qwertymk
@qwertymk- 哎呀!错过了那个链接。感谢你指出来! - templatetypedef

3
首先需要提到的是,JavaScript 是完全单线程的。模拟多线程并不是一个好的选择,更好的方法是依靠事件循环。
如上所述,Web Workers 可以使用,但实际上并没有真正的跨浏览器兼容性,因此我会忽略 Web Workers。另外,你不能使用 Web Workers 进行任何 DOM 操作。
我建议看看 node.js,了解事件循环为什么是替代多线程的好方法。我相信他在这个 video 中很好地解释了为什么它是一个好的替代方案。
因此,不要使用函数数组并迭代它们,而是创建一个事件并将一组函数绑定到该事件并触发该事件。backbone.js 中可以找到非常轻量级的事件实现。
在 JavaScript 中,你不能暂停线程,因为只有一个线程。除非函数中有点,否则没有挂起或恢复函数的方法。
只有一种方法可以模拟这个过程。编写一个JavaScript解析器,将您精心构建的JavaScript解析并建立一个系统,使您能够暂停和恢复JavaScript代码。
例如,考虑以下函数:
function(i) {
    j = i + 1;
    console.log(j);
    return foo(j);
}

并将其转换为这个。
var bar = function(i) {
    var r = {};
    var j = i + 1; 
    var f = function() {
         console.log(j);
         var g = function() {
              return foo(j);
         };
         onNext(g, arguments.callee, this, r);
    };
    onNext(f, arguments.callee, this);
    return r;
}

你需要使用.suspend.resume扩展Function。

Function.prototype.suspend = function() {
     this.__suspended = true;
}

Function.prototype.resume = function() {
     this.__suspended = false;
}

function onNext(callback, function, context, returnObj) {
     if (!function.__suspended) {
          var cb = function() {
              Backbone.Events.unbind("run", cb);
              returnObj.r = callback.call(this);
          }
          Backbone.Events.bind("run", cb);
     }
}

setInterval(function() {
     Backbone.Events.trigger("run");
}, 5);

此外,您需要将所有对var a = b()的引用替换为

callFunctionAsync(b, context, args, function(return) {
    var a = return;
    ...
});

我会把实现留给你。目前,所有函数都返回一个对象r,只有当r.r设置为一个值时才会“返回”。因此,在事件循环周围检查它是否已经“返回”,方法是检查r.r是否已经设置,如果已经设置,则触发异步回调函数。
嘿,看看我们得到了什么。我们通过在事件循环中运行它们来模拟线程。最好直接在代码中使用事件循环,而不是通过它来模拟线程。
基本上,当你再次进入事件循环时,让你的函数运行其代码的下一行。并且在你循环事件时检查特定的“函数”是挂起还是恢复。
出于简洁起见,我没有实现将函数的返回值向上冒泡回“函数”的功能。这不应该太难模拟。
要么直接使用事件循环,要么使用虚假的线程方法,并让编译器编译你的代码,以便在编码时不会显得丑陋。
如果你引起死锁,祝你好运。

如果你正在为JVM实现线程,为什么需要让你的JavaScript也成为线程?你是否允许用户在JVM上运行JavaScript代码?这种方式似乎很不合适,很容易出错并完全破坏状态。JVM内部需要线程的任何内容都可以在JavaScript中使用事件循环进行内部实现。 - Raynos
@Raynos- 这正好相反 - 我正在使用JavaScript来实现JVM。 :-) 这是一个学术项目。挑战在于如何在JavaScript内部最佳地实现JVM级别的线程,同时还允许以JavaScript本地方式实现Java原生函数。 - templatetypedef
@templatetypedef 我的意思是你正在使用JS来模拟JVM。为什么需要让你的JavaScript运行线程?为什么不只使用事件循环,并使您的JVM低级JavaScript实现依赖于事件循环,但让您的高级Java代码假装它是线程化的,而代理到事件循环中。此外,我还取消了返回值冒泡,这真是一件麻烦的事情。 - Raynos
@templatetypedef,不要模拟线程,而是考虑使用类似$.Deferred的东西,听起来这正是你想要做的。看完整个node.js视频,你应该会对此有所了解,这只需要一种不同的思维方式。实现线程并不是必要的,只有作为学术练习才有用。 - Raynos
@templatetypedef 还有,此外。建议在node.js上进行构建,这是一种更好的方式。onNext 已经在node.js中被原生设置了,并且事件发射器将会帮助很多。你可以在node.js中生成新进程,使用 C++ 插件扩展源代码以允许真正的多线程代码。这是一个显著更强大的平台。 - Raynos
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接