进行多线程JavaScript运行时实现的缺点是什么?[关闭]


51

在过去的一周中,我一直在研究多线程JavaScript运行时实现。我有一个使用JavaScriptCore和boost的C ++概念证明。

该体系结构很简单:当运行时完成对主脚本的评估后,它将启动并加入线程池,该线程池开始从共享优先级队列中选择任务,如果两个任务尝试同时访问变量,它将被标记为atomic,并且争夺访问权限。

工作的多线程node.js运行时

问题是,当我向JavaScript程序员展示此设计时,我得到了非常负面的反馈,我也不知道为什么。即使是私下里,他们都说JavaScript是单线程的,必须重写现有的库,而如果我继续从事这一工作,gremlins将产生并吞噬一切生物。

我最初也有一个本地协程实现(使用boost上下文),但是我不得不放弃它(JavaScriptCore对于堆栈是很古怪的),并且我不想冒险,所以我决定不提它。

你怎么看?JavaScript是单线程的吗,应该单独使用吗?为什么每个人都反对并发JavaScript运行时的想法?

编辑:该项目现在位于GitHub上,您可以自己尝试一下,让我知道您的想法。

以下是无争用地在所有CPU内核上并行运行的承诺的图片:

同时履行承诺。


7
这似乎是一个很自以为是的问题。您是否问过那些显然不喜欢您的想法的人,为什么他们认为这会带来麻烦?
5gon12eder 2016年

26
将线程添加到不需要多线程的对象中,就像将单车道转换为高速公路而不提供驾驶员的ed。大多数情况下,它会很好地工作,直到人们开始随机崩溃。有了多线程,您要么会遇到无法重现的细微计时错误,要么通常会出现不稳定的行为。您必须在设计时考虑到这一点。您需要线程同步。仅使变量成为原子并不能消除竞争条件。
mgw854 '16

2
您如何计划处理对共享状态的多线程访问?“标记为原子且争夺访问权限”并没有说明您认为这确实有效。我猜想对这个想法的否定态度是因为人们不知道您实际上是如何完成这项工作的。或者,如果您像Java或C ++那样使用适当的互斥锁来负担开发人员的全部负担,那么人们可能会思考为什么他们要在没有这种互斥锁的环境中产生这种复杂性和编程风险。
jfriend00 '16

17
因为自动协调线程之间的随机状态被认为是几乎不可能的问题,所以您没有信誉可以提供自动执行的任何功能。而且,如果您只是像Java或C ++那样将负担重担给开发人员,那么大多数node.js程序员都不希望这种负担-他们喜欢node.js不必为此处理大部分。如果您想要更多的同情耳朵,您将必须解释/显示在这方面您将如何以及将提供什么,以及为什么它将是有益和有用的。
jfriend00 '04 -4-12

3
请继续您的工作。我将没有多线程的语言视为玩具语言。我认为大多数JavaScript开发人员都可以使用具有单线程模型的浏览器。
Chloe

Answers:


70

1)多线程非常困难,不幸的是,到目前为止,您提出这种想法的方式意味着您严重低估了它的难度。

目前,这听起来像是您只是在向该语言“添加线程”,而后又担心如何使其正确和高效。尤其是:

如果两个任务尝试同时访问变量,则该变量将被标记为atomic,并且它们争用访问。
...
我同意原子变量不能解决所有问题,但是致力于解决同步问题是我的下一个目标。

在没有“同步问题解决方案”的情况下向Javascript添加线程就像在没有“解决附加问题的解决方案”的情况下向Javascript添加整数。这对于问题的本质是如此重要,以至于根本就没有必要讨论没有考虑特定解决方案的情况下是否值得添加多线程,无论我们多么想要它。

另外,使所有变量原子化是一种可能使多线程程序的性能比单线程程序的事情,这使得在更实际的程序上实际测试性能并查看是否获得任何东西变得更加重要。

对我来说,还不清楚您是要使线程对node.js程序员隐藏还是打算在某个时候公开它们,从而有效地为多线程编程创建新的Java语言方言。这两个选项都可能很有趣,但是听起来您甚至还没有决定要瞄准哪个。

因此,目前,您正在要求程序员考虑从单线程环境切换到全新的多线程环境,该环境没有解决同步问题的方法,也没有证据表明它可以改善实际性能,并且似乎没有解决这些问题的计划。

这可能就是为什么人们没有认真对待您的原因。

2)单事件循环的简单性和鲁棒性是一个巨大的优势。

Javascript程序员知道Java语言在竞争条件和其他困扰所有真正的多线程编程的极其隐蔽的错误中是“安全的”。他们需要强有力的论据来说服他们放弃安全性,这一事实并不能使他们胸襟开阔,而是让他们负责任。

除非您能以某种方式保持这种安全性,否则任何想切换到多线程node.js的人都可能会改用像Go这样为多线程应用程序专门设计的语言。

3)Javascript已经支持“后台线程”(WebWorkers)和异步编程,而没有直接向程序员公开线程管理。

这些功能已经解决了影响现实世界中Javascript程序员的许多常见用例,而没有放弃单个事件循环的安全性。

您是否牢记这些功能无法解决的特定用例,并且Javascript程序员想要解决方案?如果是这样,最好在该特定用例的上下文中显示您的多线程node.js。


PS:什么能说服尝试切换到多线程node.js实现?

用Javascript / node.js编写一个简单的程序,您认为它将受益于真正的多线程。在正常节点和多线程节点上的此示例程序上进行性能测试。向我展示,您的版本在很大程度上引入了运行时性能,响应速度和多个内核的使用,而没有引入任何错误或不稳定。

完成此操作后,我认为您会看到人们对该想法更感兴趣。


1
1)好的,我承认我已经推迟了一段时间的同步问题。当我说“要完成两个任务”时,这不是我的设计,主要是观察一下dl.dropboxusercontent.com/u/27714141/…—我不确定JavaScriptCore在这里执行哪种巫术,但不应该这样做如果此字符串不是天生的原子,那么该字符串会损坏吗?2)我非常不同意。这就是将JS视为玩具语言的原因。3)如果使用线程池调度程序实现,则ES6 Promise的性能将更高。
voodooattack '16

13
@voodooattack“这就是将JS视为玩具语言的原因。” 不,这就是将其视为玩具语言的原因。每天都有成千上万的人使用JS,并且对它,缺点和全部感到非常满意。确保您正在解决其他人实际上已经遇到的问题,而仅通过更改语言无法更好地解决这个问题。
克里斯·海斯

@ChrisHayes问题是,为什么我可以解决它们时就忍受它的缺点?并发功能是否可以改善JavaScript?
voodooattack '16

1
@voodooattack就是这个问题。并发功能是否可以改善Javascript?如果您能得到社区对不是“否”的答案,那么也许您正在做些事情。但是,似乎事件循环和Node对工作线程的本机委派(用于阻止事件)足以满足人们的大部分需求。我认为,如果人们真的需要线程化Javascript,他们将使用Javascript worker。但是,如果您找到一种使工人使用一流函数而不是JS文件的方法,那么……您可能真的很感兴趣。
Lunchmeat317 '16

7
@voodooattack将JavaScript称为玩具语言的人不知道他们在说什么。它是所有语言的首选语言吗?当然不是,但是像那样消除肯定是一个错误。如果您想消除JS是一种玩具语言的概念,请在其中创建一个非平凡的生产应用程序,或指向一个现有的应用程序。无论如何,仅增加并发性都不会改变这些人的想法。
jpmc26 2016年

16

只是在这里猜测以证明您的方法存在问题。我无法针对实际的实现对其进行测试,因为任何地方都没有链接...

我说这是因为不变性并不总是由一个变量的值来表示,在一般情况下,“一个变量”不足以成为锁的范围。例如,假设我们有一个不变的变量a+b = 0(一家银行有两个帐户的余额)。下面的两个函数确保不变量始终位于每个函数的末尾(单线程JS中的执行单元)。

function withdraw(v) {
  a -= v;
  b += v;
}
function deposit(v) {
  b -= v;
  a += v;
}

现在,在多线程世界,当两个线程执行会发生什么withdraw,并deposit在同一时间?谢谢,墨菲...

(您可能拥有专门处理+ =和-=的代码,但这无济于事。在某些时候,您将在函数中具有局部状态,并且无法同时“锁定”两个变量被违反。)


编辑:如果您的代码在语义上等效于https://gist.github.com/thriqon/f94c10a45b7e0bf656781b0f4a07292a上的Go代码,我的评论是正确的;-)


这个评论是无关紧要的,但是哲学家能够锁定多个对象,但是他们却饿死了,因为他们以不一致的顺序锁定了它们。
Dietrich Epp

3
@voodooattack“我刚刚测试过……”您是否从未遇到与线程调度不一致的执行顺序?它因运行而异,因机器而异。坐下来运行一个测试(甚至一百个测试!)而没有确定操作调度的机制是没有用的。
jpmc26 2016年

4
@voodooattack问题在于,简单地测试并发是没有用的。您需要能够证明不变量将成立,否则该机制在生产中将永远不会被信任。
sapi 2016年

1
但是您没有为用户提供“负责任地使用系统”的任何工具,因为绝对没有锁定机制。使所有事物原子化会给人一种线程安全的错觉(即使不需要原子访问,您也会遭受所有事物都是原子性的性能损失),但是它实际上并不能解决thriqon在此处给出的大多数并发问题。再举一个例子,尝试在一个线程上遍历数组,而另一个线程在数组中添加或删除元素。因此,您为什么认为Array的引擎实现甚至是线程安全的?
扎克·利普顿

2
@voodooattack因此,如果用户只能将您的线程用于没有共享数据或副作用的函数(并且最好不要将其用于其他任何事情,因为正如我们在此处看到的那样,就无法确保线程安全),那么您通过Web Workers(或为Web Workers提供更多可用API的众多库之一)提供的价值是什么?Web Workers更安全,因为它们使用户无法“不负责任地”使用该系统。
扎克·立顿

15

大约十年前,Brendan Eich(JavaScript的发明者)写了一篇名为Threads Suck的文章,这绝对是JavaScript设计神话中为数不多的经典文献之一。

是否正确是另一个问题,但是我认为这对JavaScript社区如何考虑并发性有很大影响。


31
我最后要提建议的人是布伦丹·艾希(Brendan Eich)。他的整个职业生涯都基于创建JavaScript,这真是太糟糕了,我们创建了许多工具来尝试消除其固有的混乱性。想一想,由于他,世界上浪费了多少开发人员时间。
Phil Wright

12
虽然我理解您为什么会普遍拒绝他的意见,甚至不屑于JavaScript,但我不理解您的观点如何会导致他无视他在该领域的专业知识。
比利·克雷文斯

4
@BillyCravens哈哈,即使是关于Javascript的经典书籍:Crockford 撰写的“ Javascript 的优秀部分 ”也以标题取代了游戏。以同样的方式对待Eich的态度,只是坚持他所说的好话:-)
gbjbaanb

13
@PhilWright:他的Javascript的第一个实现是Lisp变体。对我而言,这赢得了他的极大敬意。他的老板决定强迫他用类似C的语法替换Lisp语法不是他的错。Javascript的核心仍然是Lisp运行时。
slebetman '16

7
@PhilWright:您不应该因为语言的拙劣而责怪Eich。这是早期实现中的错误,并且需要向后兼容的Web语言阻止了JS的成熟。核心概念仍然构成一门奇妙的语言。
Bergi 2016年

8

原子访问不会转换为线程安全的行为。

一个示例是在更新过程中全局数据结构需要无效时,例如重新哈希哈希图(例如,向对象添加属性时)或对全局数组进行排序。在这段时间内,您不能允许任何其他线程访问该变量。这基本上意味着您需要检测整个读取-更新-写入周期并将其锁定。如果更新不重要,最终将导致问题停顿。

Javascript从一开始就是单线程的,并且经过沙盒处理,所有代码在编写时都考虑了这些假设。

就隔离的上下文和让2个单独的上下文在不同的线程中运行而言,这具有很大的优势。我也意味着编写JavaScript的人不需要知道如何处理竞争条件和其他各种多线程pitfal。


这就是为什么我认为Scheduler API应该更多地放在高级方面,并在内部用于没有副作用的Promise和函数。
voodooattack '16

6

您的方法会大大提高性能吗?

疑。您确实需要证明这一点。

您的方法是否会使编写代码更容易/更快?

绝对不是,多线程代码比单线程代码难得多很多。

您的方法会更强大吗?

僵局,比赛条件等都是解决的噩梦。


2
尝试使用node.js构建raytracer,现在使用线程再次尝试。多进程不是万能的。
voodooattack

8
@voodooattack,没有一个人会使用或不使用线程来使用javascript编写光线跟踪器,因为它是一种相对简单但运算量大的算法,最好用完全编译的语言编写,最好是支持SIMD的语言。对于使用javascript的这类问题,多进程已绰绰有余。
Jan Hudec

@JanHudec:嘿,JS也将获得SIMD支持:-) hacks.mozilla.org/2014/10/introducing-simd-js
Bergi 2016年

2
@voodooattack如果您不了解它们,请看一下SharedArrayBuffers。JavaScript正在获得更多的并发构造,但是在添加它们时要格外谨慎,以解决特定的痛点,以尽量减少做出我们不得不忍受多年的不良设计决策。
REINSTATE MONICA-杰里米银行

2

您的实现不仅涉及引入并发,还涉及引入实现并发的特定方法,即通过共享可变状态进行并发。在历史过程中,人们一直在使用这种类型的并发,这导致了许多问题。当然,您可以创建简单的程序,以使用共享的可变状态并发完美地工作,但是对任何机制的真正测试都不是它能做的,而是可以随着程序变得复杂以及向程序中添加越来越多的功能而扩展。请记住,软件不是您一次构建并完成的静态事物,而是随着时间的推移不断发展,如果有任何机制或概念可以做到的话,

您可以查看其他并发模型(例如,消息传递),以帮助您弄清楚这些模型提供了哪些好处。


1
我认为ES6承诺将从我的实现模型中受益,因为它们不争夺访问权限。
voodooattack '16

看一下WebWorkers规范。有npm软件包提供了它们的实现,但是您可以将它们作为引擎的核心而不是作为软件包来实现
Ankur 2016年

JavaScriptCore(我正在使用的JS的Webkit实现)已经实现了它们,这只是一个编译标志。
voodooattack

好。WebWorkers与消息传递并发。您可以尝试使用它们的raytracer示例,并将其与可变状态方法进行比较。
Ankur

4
消息传递总是在可变状态之上实现的,这意味着它会比较慢。我在这里看不到你的意思。:-/
voodooattack

1

这是必需的。节点js中缺少低级并发机制,这限制了它在数学和生物信息学等领域的应用。此外,线程并发不一定与节点中使用的默认并发模型冲突。对于具有主事件循环的环境中的线程,存在众所周知的语义,例如ui框架(和nodejs),并且在大多数情况下,它们仍然仍然有效,因此它们绝对过于复杂。

当然,您的普通Web应用程序将不需要线程,但是尝试做一些不那么常规的事情,并且缺少合理的低级并发原语会迅速将您带入其他确实提供它的东西。


4
但是“数学和生物信息学”最好用C#,JAVA或C ++编写
Ian

实际上,Python和Perl是这些领域的主要语言。
dryajov

1
实际上,我实现此功能的主要原因是由于机器学习应用程序。所以你有一点。
voodooattack

@dryajov很高兴您不知道某些计算科学领域的FORTRAN有多大……
cmaster

@dryajov因为它们可能是不是全职程序员的人更容易访问,而不是因为它们天生就擅长基因组测序-我们有专门构建的语言(如R)和已编译的科学语言(如Fortran)。

0

我真的相信这是因为这是一个不同而强大的想法。您正在违背信念体系。事物通过网络影响而被接受或流行,而并非基于功绩。也没有人愿意适应新的堆栈。人们会自动拒绝过于不同的事物。

如果您想出一种方法将其制作为常规的npm模块,听起来似乎不太可能,那么您可能会吸引一些人使用它。


您是说JS程序员被供应商锁定到npm吗?
voodooattack

3
问题是关于一个新的运行时系统,而不是一些npm模块,而且可变的共享状态并发确实是一个旧想法。
Ankur

1
@voodooattack我的意思是说,他们被已经流行的方法束之高阁,几乎不可能克服现状偏差。
Jason Livesay
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.