Javascript 异步函数和 Web worker 之间的区别?
Difference between Javascript async functions and Web workers?
在线程方面,web worker 和声明为
的函数有什么区别
async function xxx()
{
}
?
我知道网络工作者是在不同的线程上执行的,但是异步函数呢?这些函数的线程化方式是否与通过 setInterval 执行的函数相同,或者它们是否受制于另一种不同类型的线程化?
与 WebWorkers
相比,async
函数永远无法保证在单独的线程上执行。
他们只是在响应到达之前不会阻塞整个线程。您可以将它们视为被注册为 waiting 结果,让其他代码执行,当它们的响应通过时它们被执行;因此得名 异步 编程。
这是通过消息队列实现的,消息队列是要处理的消息列表。每条消息都有一个关联的函数,该函数被调用以处理消息。
这样做:
setTimeout(() => {
console.log('foo')
}, 1000)
将简单地将回调函数(记录到控制台)添加到消息队列。当它的 1000ms 计时器过去时,消息从消息队列中弹出并执行。
当计时器计时时,其他代码可以自由执行。这就是多线程的 错觉。
上面的 setTimeout
示例使用了回调。 Promises
和 async
在较低级别上的工作方式相同 — 它们依托于 message-queue 概念,但只是语法上不同。
async
函数只是语法糖
Promises 和它们是回调的包装器。
// v await is just syntactic sugar
// v Promises are just wrappers
// v functions taking callbacks are actually the source for the asynchronous behavior
await new Promise(resolve => setTimeout(resolve));
现在可以通过代码立即回调回调,例如如果你 .filter
一个数组,或者引擎可以在某个地方内部存储回调。然后,当特定的 事件 发生时,它会执行回调。可以说这些是 异步回调 ,而那些通常是我们包装到 Promises 中的回调 await
它们。
为了确保两个回调不会同时运行(这会使并发修改成为可能,这会造成很多麻烦)每当事件发生时,事件不会立即得到处理,而是a Job (callback with arguments) gets placed into a Job Queue. Whenever the JavaScript Agent (= thread²) 完成当前作业的执行,它会在该队列中查找下一个要处理的作业¹。
因此可以说 async function
只是一种表达 连续工作系列的方式 .
async function getPage() {
// the first job starts fetching the webpage
const response = await fetch("https://whosebug.com"); // callback gets registered under the hood somewhere, somewhen an event gets triggered
// the second job starts parsing the content
const result = await response.json(); // again, callback and event under the hood
// the third job logs the result
console.log(result);
}
// the same series of jobs can also be found here:
fetch("https://whosebug.com") // first job
.then(response => response.json()) // second job / callback
.then(result => console.log(result)); // third job / callback
虽然两个作业不能 运行 在一个代理(= 线程)上并行,但一个异步函数的作业可能 运行 在另一个作业之间。因此,两个异步函数可以 运行 concurrently.
现在谁产生这些异步事件?这取决于您在 async
函数中等待的内容(或者更确切地说:您注册的回调)。如果它是一个计时器 (setTimeout
),则设置一个内部计时器,并且 JS-thread 继续执行其他作业,直到计时器完成,然后执行传递的回调。其中一些,尤其是在 Node.js 环境中 (fetch
、fs.readFile
) 将在 内部 启动另一个线程。当线程完成时(通过事件),您只需交出一些参数并接收结果。
要获得真正的并行性,即 运行同时执行两个作业,需要多个代理。 WebWorkers
正是 - 特工。因此,WebWorker 中的代码 运行 是独立的(有自己的作业队列和执行器)。
代理可以通过事件相互通信,您可以通过回调对这些事件做出反应。当然,如果将回调包装到 Promises 中,您也可以 await
来自另一个代理的操作:
const workerDone = new Promise(res => window.onmessage = res);
(async function(){
const result = await workerDone;
//...
})();
长话短说:
JS <---> callbacks / promises <--> internal Thread / Webworker
¹ 还为这种行为创造了其他术语,例如 事件循环/队列 等。术语 Job 由 ECMA262 指定。
² 引擎如何实现代理由引擎决定,但由于一个代理一次只能执行一个作业,因此每个代理有一个线程非常有意义。
Workers 也可以通过异步代码(即 Promises)访问,但是 Workers 是 CPU 密集型任务的解决方案,这些任务会阻塞 JS 代码 运行 所在的线程;即使这个 CPU 密集函数是异步调用的。
因此,如果您有像 renderThread(duration)
这样的 CPU 密集函数,并且如果您确实喜欢
new Promise((v,x) => setTimeout(_ => (renderThread(500), v(1)),0)
.then(v => console.log(v);
new Promise((v,x) => setTimeout(_ => (renderThread(100), v(2)),0)
.then(v => console.log(v);
即使第二个花费更少的时间完成它也只会在第一个释放 CPU 线程后调用。所以我们将首先在控制台上获得 1
,然后是 2
。
然而,如果这两个函数在不同的 Worker 上 运行,那么我们期望的结果将是 2
和 1
,因为它们可以同时 运行 而第二个完成并且 returns 一条消息更早。
因此,对于基本的 IO 操作,标准的单线程异步代码非常高效,并且需要使用 Worker 是因为需要使用 CPU 密集且可以分段(一次分配给多个 Worker)的任务,例如作为 FFT 等等。
异步函数与网络工作者或节点子进程无关——与那些不同,它们不是多线程并行处理的解决方案。
一个 async function
只是 1 个函数的语法糖 returning 一个 promise then()
链。
async function example() {
await delay(1000);
console.log("waited.");
}
与
一样
function example() {
return Promise.resolve(delay(1000)).then(() => {
console.log("waited.");
});
}
这两个在行为上几乎没有区别。 await
的语义或根据承诺指定的语义,每个 async function
对其结果做 return 承诺。
1:语法糖在 if
/else
或循环更难表达为线性承诺链,但它在概念上仍然相同。
Are such functions threaded in the same way as a function executed through setInterval
is?
是的,async function
s 运行 的异步部分作为标准事件循环中的(承诺)回调。上面示例中的 delay
将使用正常的 setTimeout
实现 - 包装在易于使用的承诺中:
function delay(t) {
return new Promise(resolve => {
setTimeout(resolve, t);
});
}
我想对我的问题添加我自己的答案,根据我从所有其他人的答案中收集到的理解:
最终,除了 web worker 之外,所有回调都是美化的回调。异步函数中的代码、通过 promises 调用的函数、通过 setInterval 调用的函数等等——所有这些都在主线程中执行,使用类似于上下文切换的机制。根本不存在并行性。
真正的并行执行及其所有优点和缺陷,仅适用于网络工作者和网络工作者。
(可惜 - 我认为 "async functions" 我们终于得到了精简和 "inline" 线程)
这是一种调用标准函数作为工作程序的方法,可实现真正的并行性。这是一个在撒旦的帮助下用鲜血编写的邪恶黑客,可能有大量的浏览器怪癖可以破坏它,但据我所知它有效。
[constraints: 函数头必须像 function f(a,b,c) 一样简单,如果有的话结果,它必须经过 return 语句]
function Async(func, params, callback)
{
// ACQUIRE ORIGINAL FUNCTION'S CODE
var text = func.toString();
// EXTRACT ARGUMENTS
var args = text.slice(text.indexOf("(") + 1, text.indexOf(")"));
args = args.split(",");
for(arg of args) arg = arg.trim();
// ALTER FUNCTION'S CODE:
// 1) DECLARE ARGUMENTS AS VARIABLES
// 2) REPLACE RETURN STATEMENTS WITH THREAD POSTMESSAGE AND TERMINATION
var body = text.slice(text.indexOf("{") + 1, text.lastIndexOf("}"));
for(var i = 0, c = params.length; i<c; i++) body = "var " + args[i] + " = " + JSON.stringify(params[i]) + ";" + body;
body = body + " self.close();";
body = body.replace(/return\s+([^;]*);/g, 'self.postMessage(); self.close();');
// CREATE THE WORKER FROM FUNCTION'S ALTERED CODE
var code = URL.createObjectURL(new Blob([body], {type:"text/javascript"}));
var thread = new Worker(code);
// WHEN THE WORKER SENDS BACK A RESULT, CALLBACK AND TERMINATE THE THREAD
thread.onmessage =
function(result)
{
if(callback) callback(result.data);
thread.terminate();
}
}
因此,假设您具有这种潜在的 cpu 密集型功能...
function HeavyWorkload(nx, ny)
{
var data = [];
for(var x = 0; x < nx; x++)
{
data[x] = [];
for(var y = 0; y < ny; y++)
{
data[x][y] = Math.random();
}
}
return data;
}
...您现在可以这样称呼它:
Async(HeavyWorkload, [1000, 1000],
function(result)
{
console.log(result);
}
);
在线程方面,web worker 和声明为
的函数有什么区别async function xxx()
{
}
?
我知道网络工作者是在不同的线程上执行的,但是异步函数呢?这些函数的线程化方式是否与通过 setInterval 执行的函数相同,或者它们是否受制于另一种不同类型的线程化?
与 WebWorkers
相比,async
函数永远无法保证在单独的线程上执行。
他们只是在响应到达之前不会阻塞整个线程。您可以将它们视为被注册为 waiting 结果,让其他代码执行,当它们的响应通过时它们被执行;因此得名 异步 编程。
这是通过消息队列实现的,消息队列是要处理的消息列表。每条消息都有一个关联的函数,该函数被调用以处理消息。
这样做:
setTimeout(() => {
console.log('foo')
}, 1000)
将简单地将回调函数(记录到控制台)添加到消息队列。当它的 1000ms 计时器过去时,消息从消息队列中弹出并执行。
当计时器计时时,其他代码可以自由执行。这就是多线程的 错觉。
上面的 setTimeout
示例使用了回调。 Promises
和 async
在较低级别上的工作方式相同 — 它们依托于 message-queue 概念,但只是语法上不同。
async
函数只是语法糖
Promises 和它们是回调的包装器。
// v await is just syntactic sugar
// v Promises are just wrappers
// v functions taking callbacks are actually the source for the asynchronous behavior
await new Promise(resolve => setTimeout(resolve));
现在可以通过代码立即回调回调,例如如果你 .filter
一个数组,或者引擎可以在某个地方内部存储回调。然后,当特定的 事件 发生时,它会执行回调。可以说这些是 异步回调 ,而那些通常是我们包装到 Promises 中的回调 await
它们。
为了确保两个回调不会同时运行(这会使并发修改成为可能,这会造成很多麻烦)每当事件发生时,事件不会立即得到处理,而是a Job (callback with arguments) gets placed into a Job Queue. Whenever the JavaScript Agent (= thread²) 完成当前作业的执行,它会在该队列中查找下一个要处理的作业¹。
因此可以说 async function
只是一种表达 连续工作系列的方式 .
async function getPage() {
// the first job starts fetching the webpage
const response = await fetch("https://whosebug.com"); // callback gets registered under the hood somewhere, somewhen an event gets triggered
// the second job starts parsing the content
const result = await response.json(); // again, callback and event under the hood
// the third job logs the result
console.log(result);
}
// the same series of jobs can also be found here:
fetch("https://whosebug.com") // first job
.then(response => response.json()) // second job / callback
.then(result => console.log(result)); // third job / callback
虽然两个作业不能 运行 在一个代理(= 线程)上并行,但一个异步函数的作业可能 运行 在另一个作业之间。因此,两个异步函数可以 运行 concurrently.
现在谁产生这些异步事件?这取决于您在 async
函数中等待的内容(或者更确切地说:您注册的回调)。如果它是一个计时器 (setTimeout
),则设置一个内部计时器,并且 JS-thread 继续执行其他作业,直到计时器完成,然后执行传递的回调。其中一些,尤其是在 Node.js 环境中 (fetch
、fs.readFile
) 将在 内部 启动另一个线程。当线程完成时(通过事件),您只需交出一些参数并接收结果。
要获得真正的并行性,即 运行同时执行两个作业,需要多个代理。 WebWorkers
正是 - 特工。因此,WebWorker 中的代码 运行 是独立的(有自己的作业队列和执行器)。
代理可以通过事件相互通信,您可以通过回调对这些事件做出反应。当然,如果将回调包装到 Promises 中,您也可以 await
来自另一个代理的操作:
const workerDone = new Promise(res => window.onmessage = res);
(async function(){
const result = await workerDone;
//...
})();
长话短说:
JS <---> callbacks / promises <--> internal Thread / Webworker
¹ 还为这种行为创造了其他术语,例如 事件循环/队列 等。术语 Job 由 ECMA262 指定。
² 引擎如何实现代理由引擎决定,但由于一个代理一次只能执行一个作业,因此每个代理有一个线程非常有意义。
Workers 也可以通过异步代码(即 Promises)访问,但是 Workers 是 CPU 密集型任务的解决方案,这些任务会阻塞 JS 代码 运行 所在的线程;即使这个 CPU 密集函数是异步调用的。
因此,如果您有像 renderThread(duration)
这样的 CPU 密集函数,并且如果您确实喜欢
new Promise((v,x) => setTimeout(_ => (renderThread(500), v(1)),0)
.then(v => console.log(v);
new Promise((v,x) => setTimeout(_ => (renderThread(100), v(2)),0)
.then(v => console.log(v);
即使第二个花费更少的时间完成它也只会在第一个释放 CPU 线程后调用。所以我们将首先在控制台上获得 1
,然后是 2
。
然而,如果这两个函数在不同的 Worker 上 运行,那么我们期望的结果将是 2
和 1
,因为它们可以同时 运行 而第二个完成并且 returns 一条消息更早。
因此,对于基本的 IO 操作,标准的单线程异步代码非常高效,并且需要使用 Worker 是因为需要使用 CPU 密集且可以分段(一次分配给多个 Worker)的任务,例如作为 FFT 等等。
异步函数与网络工作者或节点子进程无关——与那些不同,它们不是多线程并行处理的解决方案。
一个 async function
只是 1 个函数的语法糖 returning 一个 promise then()
链。
async function example() {
await delay(1000);
console.log("waited.");
}
与
一样function example() {
return Promise.resolve(delay(1000)).then(() => {
console.log("waited.");
});
}
这两个在行为上几乎没有区别。 await
的语义或根据承诺指定的语义,每个 async function
对其结果做 return 承诺。
1:语法糖在 if
/else
或循环更难表达为线性承诺链,但它在概念上仍然相同。
Are such functions threaded in the same way as a function executed through
setInterval
is?
是的,async function
s 运行 的异步部分作为标准事件循环中的(承诺)回调。上面示例中的 delay
将使用正常的 setTimeout
实现 - 包装在易于使用的承诺中:
function delay(t) {
return new Promise(resolve => {
setTimeout(resolve, t);
});
}
我想对我的问题添加我自己的答案,根据我从所有其他人的答案中收集到的理解:
最终,除了 web worker 之外,所有回调都是美化的回调。异步函数中的代码、通过 promises 调用的函数、通过 setInterval 调用的函数等等——所有这些都在主线程中执行,使用类似于上下文切换的机制。根本不存在并行性。
真正的并行执行及其所有优点和缺陷,仅适用于网络工作者和网络工作者。
(可惜 - 我认为 "async functions" 我们终于得到了精简和 "inline" 线程)
这是一种调用标准函数作为工作程序的方法,可实现真正的并行性。这是一个在撒旦的帮助下用鲜血编写的邪恶黑客,可能有大量的浏览器怪癖可以破坏它,但据我所知它有效。
[constraints: 函数头必须像 function f(a,b,c) 一样简单,如果有的话结果,它必须经过 return 语句]
function Async(func, params, callback)
{
// ACQUIRE ORIGINAL FUNCTION'S CODE
var text = func.toString();
// EXTRACT ARGUMENTS
var args = text.slice(text.indexOf("(") + 1, text.indexOf(")"));
args = args.split(",");
for(arg of args) arg = arg.trim();
// ALTER FUNCTION'S CODE:
// 1) DECLARE ARGUMENTS AS VARIABLES
// 2) REPLACE RETURN STATEMENTS WITH THREAD POSTMESSAGE AND TERMINATION
var body = text.slice(text.indexOf("{") + 1, text.lastIndexOf("}"));
for(var i = 0, c = params.length; i<c; i++) body = "var " + args[i] + " = " + JSON.stringify(params[i]) + ";" + body;
body = body + " self.close();";
body = body.replace(/return\s+([^;]*);/g, 'self.postMessage(); self.close();');
// CREATE THE WORKER FROM FUNCTION'S ALTERED CODE
var code = URL.createObjectURL(new Blob([body], {type:"text/javascript"}));
var thread = new Worker(code);
// WHEN THE WORKER SENDS BACK A RESULT, CALLBACK AND TERMINATE THE THREAD
thread.onmessage =
function(result)
{
if(callback) callback(result.data);
thread.terminate();
}
}
因此,假设您具有这种潜在的 cpu 密集型功能...
function HeavyWorkload(nx, ny)
{
var data = [];
for(var x = 0; x < nx; x++)
{
data[x] = [];
for(var y = 0; y < ny; y++)
{
data[x][y] = Math.random();
}
}
return data;
}
...您现在可以这样称呼它:
Async(HeavyWorkload, [1000, 1000],
function(result)
{
console.log(result);
}
);