JavaScript 变量上的事件循环操作是否阻塞?
Are JavaScript event loop operations on variables blocking?
在 JavaScript 的 non-blocking 事件循环中,读取然后更改变量是否安全?如果两个进程几乎同时想要更改一个变量会怎样?
示例 A:
- 过程 1:获取变量 A(为 100)
- 过程2:获取变量A(为100)
- 进程1:加1(即101)
- 过程 2:加 1(即 101)
- 结果:变量 A 是 101 而不是 102
这是一个简化的例子,有一条 Express 路线。假设这条路线每秒被调用 1000 次:
let counter = 0;
const getCounter = () => {
return counter;
};
const setCounter = (newValue) => {
counter = newValue;
};
app.get('/counter', (req, res) => {
const currentValue = getCounter();
const newValue = currentValue + 1;
setCounter(newValue);
});
示例 B:
如果我们做一些更复杂的事情,比如 Array.findIndex()
然后 Array.splice()
会怎样?会不会是找到的索引已经过时了,因为另一个 event-process 已经改变了数组?
- 处理A findIndex(为12000)
- 进程B findIndex(是34000)
- 进程A拼接索引12000
- 进程B拼接索引34000
- 结果:进程 B 删除了错误的索引,应该删除 33999 而不是
const veryLargeArray = [
// ...
];
app.get('/remove', (req, res) => {
const id = req.query.id;
const i = veryLargeArray.findIndex(val => val.id === id);
veryLargeArray.splice(i, 1);
});
示例 C:
如果我们在示例 B 中添加一个异步操作会怎么样?
const veryLargeArray = [
// ...
];
app.get('/remove', (req, res) => {
const id = req.query.id;
const i = veryLargeArray.findIndex(val => val.id === id);
someAsyncFunction().then(() => {
veryLargeArray.splice(i, 1);
});
});
这个问题很难找到合适的词来描述它。请随时更新标题。
根据@ThisIsNoZaku 的 link,Javascript 有一个 'Run To Completion' 原则:
Each message is processed completely before any other message is processed.
This offers some nice properties when reasoning about your program, including the fact that whenever a function runs, it cannot be pre-empted and will run entirely before any other code runs (and can modify data the function manipulates). This differs from C, for instance, where if a function runs in a thread, it may be stopped at any point by the runtime system to run some other code in another thread.
A downside of this model is that if a message takes too long to complete, the web application is unable to process user interactions like click or scroll. The browser mitigates this with the "a script is taking too long to run" dialog. A good practice to follow is to make message processing short and if possible cut down one message into several messages.
进一步阅读:https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop
因此,对于:
示例 A:这作为站点计数器非常有效。
示例 B:这也可以正常工作,但是如果同时发生许多请求,那么最后提交的请求将等待相当长的时间。
示例 C:如果在 someAsyncFunction
完成之前发送了对 \remove
的另一个调用,那么您的数组完全有可能无效。解决这个问题的方法是将索引查找移动到异步函数的 .then
子句中。
IMO,以延迟为代价,这解决了许多潜在的痛苦并发问题。如果您必须优化请求的速度,那么我的建议是研究不同的架构(附加缓存等)。
在 JavaScript 的 non-blocking 事件循环中,读取然后更改变量是否安全?如果两个进程几乎同时想要更改一个变量会怎样?
示例 A:
- 过程 1:获取变量 A(为 100)
- 过程2:获取变量A(为100)
- 进程1:加1(即101)
- 过程 2:加 1(即 101)
- 结果:变量 A 是 101 而不是 102
这是一个简化的例子,有一条 Express 路线。假设这条路线每秒被调用 1000 次:
let counter = 0;
const getCounter = () => {
return counter;
};
const setCounter = (newValue) => {
counter = newValue;
};
app.get('/counter', (req, res) => {
const currentValue = getCounter();
const newValue = currentValue + 1;
setCounter(newValue);
});
示例 B:
如果我们做一些更复杂的事情,比如 Array.findIndex()
然后 Array.splice()
会怎样?会不会是找到的索引已经过时了,因为另一个 event-process 已经改变了数组?
- 处理A findIndex(为12000)
- 进程B findIndex(是34000)
- 进程A拼接索引12000
- 进程B拼接索引34000
- 结果:进程 B 删除了错误的索引,应该删除 33999 而不是
const veryLargeArray = [
// ...
];
app.get('/remove', (req, res) => {
const id = req.query.id;
const i = veryLargeArray.findIndex(val => val.id === id);
veryLargeArray.splice(i, 1);
});
示例 C:
如果我们在示例 B 中添加一个异步操作会怎么样?
const veryLargeArray = [
// ...
];
app.get('/remove', (req, res) => {
const id = req.query.id;
const i = veryLargeArray.findIndex(val => val.id === id);
someAsyncFunction().then(() => {
veryLargeArray.splice(i, 1);
});
});
这个问题很难找到合适的词来描述它。请随时更新标题。
根据@ThisIsNoZaku 的 link,Javascript 有一个 'Run To Completion' 原则:
Each message is processed completely before any other message is processed.
This offers some nice properties when reasoning about your program, including the fact that whenever a function runs, it cannot be pre-empted and will run entirely before any other code runs (and can modify data the function manipulates). This differs from C, for instance, where if a function runs in a thread, it may be stopped at any point by the runtime system to run some other code in another thread.
A downside of this model is that if a message takes too long to complete, the web application is unable to process user interactions like click or scroll. The browser mitigates this with the "a script is taking too long to run" dialog. A good practice to follow is to make message processing short and if possible cut down one message into several messages.
进一步阅读:https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop
因此,对于:
示例 A:这作为站点计数器非常有效。
示例 B:这也可以正常工作,但是如果同时发生许多请求,那么最后提交的请求将等待相当长的时间。
示例 C:如果在 someAsyncFunction
完成之前发送了对 \remove
的另一个调用,那么您的数组完全有可能无效。解决这个问题的方法是将索引查找移动到异步函数的 .then
子句中。
IMO,以延迟为代价,这解决了许多潜在的痛苦并发问题。如果您必须优化请求的速度,那么我的建议是研究不同的架构(附加缓存等)。