js引擎是单线程,那异步有什么优势呢?(已被解答)
发布于 5 年前 作者 ailuhaosi 3703 次浏览 来自 问答

看了一篇介绍js引擎的写的很详细,下面第一篇。 js引擎超详细介绍从输入url到服务器返回页面,整个过程经历了什么,超完整,超详细 第二个能验证你的前端是否体系完整!!! 但,我看完还有一个疑问,链接中提到:js是单线程的,只有当所有同步任务执行完,才会执行异步任务

我的问题:那异步方式优势是什么呢?

  1. 异步写法:
const fs = require('fs');
fs.writeFile('test.txt', 1234567899876543210, 'utf8', function (err) {
  if (err) throw err;
  else console.log('写完成');
});
for(let i = 0;i < 1000000;i++){
  console.log( `hello=${i}` );
}
  1. 结果:文件夹test.txt会很快创建,但之后就一直输出hello=···,直到最后才会在test.txt中写入内容,并输出写完成
  2. 同步写法:
const fs = require('fs');
for(let i = 0;i < 1000000;i++){
  console.log( `hello=${i}` );
}
fs.writeFileSync('test.txt', 1234567899876543210, 'utf8');
console.log('写完成');

我的问题:异步写法与同步写法有什么区别呢?在性能上?仅仅是写的时候顺序不同么?

虽然是老生常谈的问题,但自己一直不是特别懂到底异步好在哪里,之前只是人云亦云的用,求解答?

14 回复

writeFileSync 是同步代码 ,一旦执行后node.js 事件循环, 触发其他操作全都无法执行 异步回调可以 防止其他逻辑被阻滞

事件循环里都是异步代码吧,那事件循环里放的异步代码,都改成同步写法,不是也一样么。当然点击事件等是改不了的。

同步阻塞主线程, 必须等待 fs.writeFileSync 执行结束后才能再次进入调用 fs.writeFileSync 异步非阻塞主线程, 执行了 fs.writeFile 在没有结果返回的时候可以再次进入相同代码执行 fs.writeFile。 这样就实现了并发操作。

以下是我的理解:不知对否? 一次事件循环中的异步操作,会一次性放到js引擎的执行栈中进行执行。 如:多个异步操作fs.writeFile写内容的过程,js引擎占用的cpu会每隔一个时间片先执行一下1、再执行一下2、再执行一下3,依次类推,我们看起来的效果好像是并发的,然后哪个异步先完成就先输出回调结果。

fs.writeFile(···);//1
fs.writeFile(···);//2
fs.writeFile(···);//3

结果是:看起来像并发,其实按照所有代码任务都执行完为标准看,异步不会比同步提高性能,因为异步的时候与同步的时候分配的cpu没变。

以前看过的介绍nodejs适用场景:适合IO密集型,不适合cpu密集型。看死月那不c++扩展中介绍,底层的异步实现并非单纯的单线程,是由libuv(linux)将不同任务分配给不同线程,以异步方式返回给v8

我的理解:nodejs适合读写型,不适合计算型

@liuzhiguo11 这个理解其实有偏差。 个人看法:对于计算密集型nidejs并非不适合。除开语言本身密集计算能力高低,对于密集计算场景多线程意义可能不是想象那么高。

开一个http服务,如果第一个人访问一个接口时被一个同步方法调用阻塞了,那第二个人访问也会被阻塞,这算一个单线程同步调用的缺点,异步的好处。

首先,你得知道阻塞(blocking)和非阻塞(non-blocking)有啥区别。

你可以把Node.js看做一个人+一个团队:

  1. JS的执行部分看做是一个人,叫他小杰;
  2. 调用libuv实现的IO操作的部分看做是一个团队,比如李家团;

小杰只有他自己1个人(单线程),他同一时间内只能干一件事,这部分是阻塞的,一项工作(语句)执行完才能执行下一项,比如一个for循环:

for(let t=0;t<10000;t++){
	console.log(t);
}

因为只有第一次循环执行完才能执行第二次循环,第二次循环执行完才能执行第三次……所以小杰重复做了一万次工作(累死),假设每个工作要做1秒,那么小杰做完所有工作要花1万秒。 这就叫做阻塞。

并不是所有工作都必须让小杰自己去做,比如送信(IO)这种事情就可以委托给李家团来做,李家团是一个团队,有无数个人随时等着接活。 某一天小杰想写两封信分别送给不同的人,于是他先写第一封信,花了1个小时时间,然后直接把这封信交给李家团,李家团派手下的人去送信,并承诺小杰,当送信的人回来的时候(事件),会告诉(回调)小杰信是否送到了。这时候小杰可以等着李家团的消息,但是会闲的蛋疼,于是他决定不等,了直接写第二封信。过了半小时,李家团送信的人回来了(事件触发),来到小杰门前敲小杰的们,此时小杰正在专注地写一段文字,听到敲门声,他说:“稍等一下(阻塞),这句马上写完。”等他写完了这一句(响应事件),就放下笔,去开门,李家团的人告诉他(回调)信送到了,小杰说了声谢谢就让对方回去了。然后小杰又继续拿起了笔写信,又过了半小时总算写完,又让李家团去送信…… 李家团送信的时候不会阻止(阻塞)小杰做其他工作,这就是非阻塞。

小杰第一封信写了1小时,第二封信也写了1小时,李家团送第一封信用了半小时,送第二封信我们先不看; 也就是说从小杰开始写第一封信到写完第二封信,一共花了2小时时间,如果第一封信是小杰亲自去送,假设他来回也只花半小时,那么那么他第二封信写完就会比原来晚半小时。 刚才说李家团手下人很多,就算小杰提前写好了10封信,一封一封交给李家团,李家团也可以派10个人同时送这些信,假设最远的路途来回需要40分钟,那么小杰也可以在40分钟后知晓全部10封信的运送结果。 那如果是100封信、1000封信呢?知道所有信的运送结果的时间几乎等于送得最远的一封信的来回时间。

这就是为什么Node单线程还能适用于IO密集型应用,一方面李家团足够强大能同时做多个跑腿工作,另一方面李家团和小杰之间约定了一个高效的合作流程(事件驱动的非阻塞回调)。

Node当中,fs.writeFile是把writeFile这件事委托给李家团去做了,李家团执行这个工作的时候,小杰可以做其他事情;而fs.writeFileSync虽然也是交给李家团去做,但小杰执意要等李家团回信儿了才肯做其他事情,等待的这段时间小杰是被阻塞的,没法做其他事情(不是你自己愿意等的吗喂?!)。

nodejs的异步是用多线程来实现的哦,所以你能用异步的方法就用异步,多线程总是要比单线程的快的吧。

用fs.writeFile异步去写文件内容这个过程是nodejs内部新开的线程完成的,这时候js的执行线程跟它是相互独立的,js会继续执行fs.writeFile下面的代码,这时候就可以理解为多线程同时工作了,当你写文件过程操作完,nodejs内部的事件循环在Poll阶段获得通知,如果js线程空闲,写文件的回调函数可以执行,不空闲就等它执行完同步代码在执行。我是这么理解的😊

来自酷炫的 CNodeMD

node.js 底层 v8 是 C++,支持多线程,所以 fs.writeFile 可以并发跑

你去某K买鸡腿, 店A采用同步的方式: 客户1点单完付款之后,要等客户1餐配齐了才轮到客户2 店B采用异步的方式:客户1点单完付款之后,在旁边等餐。客户1等餐的同时,就轮到客户2点餐了

你喜欢哪个店的点餐方式?

@libook 谢谢答主,辛苦的码字,用脑洞的故事清楚地解释了问题。

回到顶部