精华 线程有什么用处? 为什么有些东西注定不会流行?
发布于 22天前 作者 tulayang 1437 次浏览 来自 分享

多线程的领域也许只有一个: 图形学.

我们以一个游戏来说明


@
|___|___|___|___|___

**@是一个玩家, 往前走, 每一个___**是1米.

每当@走到1米的时候, 会绘制一个蘑菇*****给玩家看.


    @
|___*___|___|___|___

        @
|___*___*___|___|___

非常简单的游戏.

问题在这

1个游戏开启1个进程, 启动1个线程来完成游戏任务.

当**@走完1米的时候, 线程这时候要求进度停下, 开始绘制蘑菇***.

绘制的速度取决于CPU的速度.

所以, 在这里, 根据玩家机器配置, 会出现不同程度的卡顿.

多线程策略:

1个游戏开启1个进程, 启动n个线程来完成游戏任务.


 @
|___*___*___|___|___

CPU有时候运行速度是很快的, 所以当玩家在出发第一个1米走的时候, CPU已经快速的绘制完了玩家当时的场景, 并且空闲下来.

这时候可以利用这个空闲, 用第2个线程去绘制远方的**蘑菇***

这样当玩家走完1米的时候, **蘑菇***早就绘制完了, 或者快绘制玩了.

对于1核CPU-1进程-多线程的游戏, CPU在每一个任务点出现空闲, 然后切换到下一个任务点运行, 再依次切换回第一个任务点继续.

对于玩家来讲移动一个格是0.几秒的事情, 对于CPU来讲, 已经完成了成千上万的任务.

为什么有些东西不会流行?

我已经解释了线程. 再来看看nodejs.

nodejs就是一个口, 进来出去

[client]  -- [nodejs] -- [OS | DATABASE | ...]

你甚至可以把ta看成是个网关, 是个路由器, …

消息进来, 看一看, 放进去.

消息出去, 看一看, 放出去.

你认为线程对于其来讲, 意义何在?

唯一的意义?

只有你想把nodejs用来做图形渲染这样的高CPU处理.

但是不要忘了, 目前, 你只有浏览器环境下的CanvasSvg可选.

想把nodejs当成DirectX OpenGLOS层面上渲染, 没有API供你使用.

而对于浏览器环境, HTML5本身就拥有Worker用来处理WebGL.

29 回复

这么任性的帖子

玩家还没走,蘑菇怎么能长出来呢?

@waksana

长出蘑菇并不是游戏规则, 而是渲染策略.
比如玩魔兽世界, 你走到某个点, 在远处有多远的场景可以渲染.

@tulayang js本来就是单线程的呀

还不一定是线程还是 node.js 的 io 模型活到五年后。。 话说最近 linus 也喷了并行计算

@alsotang 目前事件驱动的模型在高并发场景下没有线程的稳定

@yorkie 我的意思也是不要对 node.js 的模型这么乐观

nodejs的I/O异步不都靠线程的么?

至于异步非阻塞,事件驱动的流行度,可以参考nginx的流行度,一个纯c建立的单线程,异步,事件驱动,可集群的路由服务。

如果nginx不再流行,代表一个新的方式崛起。

我倒觉得并行计算是很有前景的,因为按照通常的观点,单个 CPU 核心的频率已经几乎到了极限,为了提高计算力,只能使用多个核心。 也许现在单个核心还足够支撑大多数的程序,但程序需要的计算力是只增不减的,所以还是要发展并行计算,尤其是服务器端的程序。

@jysperm

并行计算跟多线程是两码事。事件驱动的并行是通过master调配,子进程侦听共享的套接字实现的。

多进程也是多线程的一种,业界使用多线程和多进程开发已经几十年,web 应用开发从来都是多进程或多线程的。 做为对比,nodejs 的全异步开发紧紧数年而已,的确算不得经得住考验的东西。 至于谁更流行,一目了然。

其次,nginx的全异步有其设计场景,nginx 的 cgi 接口仍是多线程/进程的同步模式。这叫半同步半异步(HSHA),fibjs采取的就是这个模式。至于 nginx 计划开发中的 js 引擎,则采用的是纤程模型,也是 fibjs 目前的运算模型。对不起,不是回调。 nodejs 完全错误地理解了 nginx 的设计理念,才搞出这么个怪物来。

@xicilion

多进程跟多线程没一点关联,好吗!

计算机本不该有多线程。Unix,Linux在思想上一直都是多进程,用于安全的隔离并行任务的副作用。

至于线程,说白了,就是微软的产物。而最终线程变得流行,是因为DirectX和游戏玩家的支持,因为在单任务处理绘图的复杂度过大,时间过长。Windows在其强盛期强行捧起了线程。

但是多线程的内存耗用和副作用,对服务器确是弊大于利,Unix、Linux内核中哪个地方是多线程设计的?

1. 无论是Unix、Linux的TCP,还是Nginx,以及MongoDB的集群,现在都是一个固定的模式,就是一个Master主进程负责控制,多个子进程负责侦听共享的套接字。Master轮询各个子进程的忙碌空闲,负责分配,子进程则实际操作。

这也是现在的多核并发异步事件驱动的主要模式。

2. 你说的线程,对于类nix任务,压根没有用处。李纳斯也从来没想过用那玩意。Nginx的模型,你真的想多了好吗?套接字编程,压根不需要你的所谓线程。如果是这样,Nginx就不会以内存占用少压过Apache。因为Apache才是真正的多线程模型。

3. 回调?我真佩服你的逻辑,你觉得Linux有没有回调?我再问问Linux的任务是不是事件驱动的?请问signal怎么解释?

有三点很明显:

* 你恐怕分不清线程和进程的生命周期的区别。 * 你该好好分析下函数式编程,或者至少是C语言。回调只是接口设计的一种方式,而不是事件驱动的主要编写方式。ta比面向对象的模板方法、状态模式对模型的抽象更有灵活性,原子级别更低。不要把回调跟事件驱动混为一谈。 * 任何以面向对象的思维,来编写Nodejs程序,都是失败的。

多进程比多线程强多了。呵呵哒。

@tulayang 第一,多进程就是多线程,你强行区分就是白马非马,生命期管理管理的差异和内存隔离只是其很小的差异。不要告诉我 Linux 引入线程是在开倒车。此外你关于线程的理解几乎都是错的,我深度怀疑你根本没用过。只是人云亦云罢了。

其二,通讯与业务隔离的模式是所有你说的这些优秀软件的标准设计模式,就是半同步半异步模式,通讯部分全异步,业务部分同步执行。你自己去看 nginx cgi,或者 mong 的业务部分,没有人全部使用异步,除了 nodejs 这个怪胎。

其三,该搞清楚,fibjs 的通讯模式和执行模式跟你所说的这些优秀模型毫无差别。通讯部分都是单线程的,执行部分都是分发出去的,并且业务逻辑都是同步的。此外我写单线程非阻塞web服务器的时候还没有 nginx。

其四,你哪只眼睛看到我说没有了?fibjs 内部的回调多到你尿裤子。但是没有那个框架设计者会像 nodejs 这样流氓地让让回调里的业务逻辑也必须回调。

最后,别拿函数式意淫nodejs。我强调 nodejs 是因为他不是js,只是js的一个运行库而已。

补充一句,单从工程上讲 nodejs 已经设计的很失败了。我用的是js,至于nodejs,对不起09年我读它源代码之前就已经摒弃它了。

对了忘了说一句,你这么憎恨线程,那把 nodejs 的多线程部分删除掉了再用吧。要不然多丢人啊哈哈。

@xicilion 第二点,nodejs业务部分也没规定必须要异步的。在极端条件下,如果业务部分有大量计算的话,是会造成明显的延迟的。 你用mongo的异步来指责nodejs并不公平,这个过程划到通讯里面似乎更为合理点。

你所提出的恰好是nodejs编程中要注意的,隔离异步,尽量将异步代码隔离到一个比较小的区域,业务逻辑尽量用同步代码实现。 我想fibjs在与数据库通讯的时候,还是需要异步的。

异步没有错,错在如何表达异步,有的人喜欢显示的,有的人不喜欢,但是如果想提升效率,异步还是必须的,因为这个世界本身就是异步的。

@xicilion

第一,多进程就是多线程,你强行区分就是白马非马,生命期管理管理的差异和内存隔离只是其很小的差异。不要告诉我 Linux 引入线程是在开倒车。此外你关于线程的理解几乎都是错的,我深度怀疑你根本没用过。只是人云亦云罢了。

错误! 多进程不是多线程. 进程是Process, 线程是Thread.

一个进程是操作系统内部完全独立的生命体, 操作系统赋予一个接口, 通过发布signal来强制结束进程. 用白话说, 1个进程就是一个软件的实体.

你打开一个魔兽世界, 会启动一个魔兽世界进程. 你再打开一个魔兽世界, 又启动魔兽世界进程. 两个进程是完全独立的, 你可以在任何一个内部挂机, 而不会影响到另一个.

这是两个进程, 而不是两个线程!

何时候出现多线程? 这取决于这个进程的内部设计. 比如, 魔兽世界可以在内部启动3个线程用于图形绘制, 他们是一个魔兽世界进程的子单位, 而不是一个软件实体.

分清多进程和多线程的一个笨办法可以这样理解

10个多进程是10个函数, 每个函数内部有自己的变量: function f1() {var a = 100;} function f2() {var a = 200;} …

10个多线程是在1个函数内, 拥有共同的全局变量:

function f() {
    var a = 100, b = 200; 
    function f1() {}  
    function f2() {} ... 
}

副作用一览无余.

你自己去看 nginx cgi,或者 mong 的业务部分,没有人全部使用异步,除了 nodejs 这个怪胎。

你可以否定nodejs接口设计的方式, 就像有些人否定浏览器document的设计. 但是这跟异步事件驱动没有半毛钱的关系.这真的只是接口设计采取的一种策略.

我也可以这样设计事件驱动:

function dosome(callback) {...}

dosome(function (eventType) {
    if (eventType === 'data') { // do something} 
    if (eventType === 'end') { // do something }
});

此外我写单线程非阻塞web服务器的时候还没有 nginx。

异步事件驱动, 既不是nginx发明的, 也不是nodejs发明的, 浏览器document在诞生时就在用, 但也不是ta发明的. 早在Unix就被设计出来了, 大家只不过是选择这个方式, 好吗?

其四,你哪只眼睛看到我说没有了?fibjs 内部的回调多到你尿裤子。但是没有那个框架设计者会像 nodejs 这样流氓地让让回调里的业务逻辑也必须回调。

好的性能, 必然有编写的损失. 为什么现在没人用javascript 或者是 python ruby写操作系统, 而仍然在用C语言的指针? 这是因为指针有内存和CPU上的性能优势.

nodejs也是如此. 回调, 意味着没有壅余的处理操作, 反应会更快, 做的事情会更少.

最后, 如何函数式编程?

如果强调函数式语法, 那就是愚蠢的. 强调"纯"也是愚蠢的. 创造外部"纯"内部"不纯"的高阶函数(解决外部副作用), 并使用小函数解决大函数的问题(解决模型抽象), 这就是函数式编程的使用路径.

@coordcn 说出来很多人不信,fibjs 的绝大部分内核逻辑都是异步的。异步的部分比 nodejs 要多得多。 nodejs 的问题不是用异步,而是任由异步向上传染,并且刻意宣扬这种错误的工程设计。

@tulayang 再说一遍,因为 nodejs 的错误的工程设计,导致其异步性能上其实很慢的。 fibjs vs nodejs web server: 7 倍 fs.readFile 3 倍 leveldb 三倍

你可以说 web server 是欺负 nodejs 没用 c,那其他的呢?

更何况当显示异步向上传染时,性能会在每一层都下降。

所以忘记因为性能,所以勉强接受不舒适的写法这样的理由吧。iis+asp 跑分都比 nodejs 高。

除此之外,其实我认为我们对很多模式的观点并没有太大差异,只是在工程上的选择不同罢了。

针对上面的讨论说几个结论吧,理论上的争论只会越走越远。

一、nodejs 很慢,不承认不行。 二、无论异步合理性有多少,其不合理性一样存在,不直观和工程混乱。 三、共享变量的付作用是多纤程和回调多个业务并行处理时都会遇到的。多纤程直观的表达会让这件事情变得更清晰和容易处理。

@xicilion

我这么说吧, 同样一个任务, 你的方式不过是在用更多的CPU占用次数, 更大的内存缓冲来提升CPU在该任务上的切换率.

在远古世纪, 当1核1进程1线程时

打开一个photoshop, 再打开一个播放器, 对于操作系统来讲, 这就是2个进程, 他们就是多进程的. 多进程的产生, 足够原始了吧, 从发明操作系统就应该设计到的.

再打开一个播放器, 对于操作系统, 这是3个进程.

至于两个播放器可不可以通过共享的TCP套接字通信, 操作系统根本不关心. 而两个播放器通过共享的TCP套接字通信, 就带有集群特性, 可以分布式.

所以, 多进程本就是很纯粹很古老的事情.

你面临的问题不是多进程的并发, 而是如何使用多进程分布同一个业务

CPU → 切换到photoshop, 完成当时的任务, 空闲 → 切换到播放器1, 完成当时的任务, 比如解压缩, 等等, 然后空闲 → 切换到播放器2, 完成当时的任务, 比如解压缩, 等等, 然后空闲 → 空闲 → 空闲 → 空闲 → 切换到photoshop, 完成当时的任务, 空闲 → 空闲 → 切换到播放器1, 完成当时的任务, 比如解压缩, 等等, 然后空闲 …

很明显, 当CPU越快, CPU出现空闲的百分比就会越多. 增加进程数, 线程数, 可以减少CPU空闲的比率.

但是有一点需要考虑清楚, 增加进程数要根据CPU内核数, 和业务压力来指定.

而增加线程数, 你要好好考虑清楚: 堆栈保存线程上下文所带来的副作用, 切换的稳定性, 堆栈保存线程上下文的内存消耗.

@xicilion “nodejs 的问题不是用异步,而是任由异步向上传染” 响马叔这句话说得特别精准。js的词法作用域(函数作用域)让回调函数可以访问其定义处的上下文。 这样回调函数在各种层次被调用时,解释器为了执行回调需要查找各种回调定义处的变量地址,这样回调的层级一多,确实比同步定义的变量查找更耗时。这是我的理解。请响马叔指正!

@countcain 解释器为了执行回调需要查找各种回调定义处的变量地址 直觉告诉我回调 没有脱离上下文.

@countcain 响马不是站在程序执行的角度说出的那句话吧?我的理解,他是站在程序编写的角度说的那句话。

@alsotang 都会活的好好的,看场景。

@alsotang 无论是代码书写,还是运行时,只要不加处理,异步都是向上传染的。

同时因为 js 是解释语言,失去了根据函数定义转变调用模式的可能,只能在调用时显式标明此次调用是异步的(callback 或者 yield 或者 $await),而如果调用模式与实现模式不同,就只好靠运气来查错了,这在大规模重构的时候会经常出现。

因此想要完全消除异步的传染,唯一的方案就是在运行时转换异步为同步。这也是 fibjs 的运行方式。

@countcain 如果单就回调和 fiber 切换,回调是更快的。但是 fiber 切换只会在异步转同步的临界处发生一次,而回调则无休无止地传染到业务的每一层,每一层回调的成本累加起来,就比一次 fiber 切换慢得太多太多了。

此外虽然我没有亲测,但是以我对 yield 执行方式的理解,yield 在性能上会比回调更慢。此判断只为猜测,不负责任。

回到顶部