Может ли код node.js привести к условиям гонки?
Из того, что я читаю, условия гонки возникают, когда разные потоки пытаются изменить общую переменную, что может привести к значению, которое невозможно при любом последовательном порядке выполнения этих потоков.
Но код в node.js работает в одном потоке, так, означает ли этот код, написанный в node.js, без условий гонки?
Ответы
Ответ 1
Нет. Это правда, что у вас не может быть условие гонки на однопроцессорной программе не в/в, выполняющей.
Но node.js в основном быстрый из-за его неблокирующего способа программирования. Неблокирование означает, что установка слушателя на событие ответа, вы можете сделать что-то еще, ожидая ответа.
Почему? Потому что работа для получения ответа выполняется в другом потоке. База данных, файловая система, работает в другом потоке, клиент, очевидно, работает на другом компьютере, и рабочий процесс программы может зависеть от
его ответ.
Так что, строго говоря, node.js работает в одном потоке, но ваш рабочий процесс программы, включающий ввод-вывод (база данных, файловая система), клиент и все, работает на многих потоках.
Таким образом, все еще может быть состояние гонки, если вы делаете запрос на добавление чего-либо в базу данных, а затем просто отправляете запрос на его удаление, не дожидаясь ответа первого запроса. Не было бы условий гонки, если бы база данных работала в том же потоке, что и node.js, и запрос был просто вызовом функции, выполняемым немедленно.
Ответ 2
Да. Node.js может работать в условиях гонки, как только вы начнете разделять ресурсы.
Я ошибочно подумал, что вы не можете получить условия гонки в Node.js, потому что это однопоточный характер, но как только вы используете общий ресурс вне node (например, файл из файловой системы), вы может попасть в состояние гонки. Я опубликовал пример этой проблемы в этом вопросе, когда я пытался понять это: node.js readfile woes
В Node.js отличается от других сред то, что у вас есть один поток выполнения JavaScript, поэтому есть только один экземпляр JavaScript, на котором запущен ваш код (в отличие от многопоточной среды, в которой есть много потоков, выполняющих ваши код приложения в то же время.)
Ответ 3
Нет. Node.js свободен от условий гонки, которые были бы вызваны переключением контекста; однако вы все равно можете написать программу Node.js, где асинхронные события, происходящие в непредвиденном порядке, приводят к несогласованному состоянию.
Например, предположим, что у вас есть две функции. Первый отправляет сообщение через WebSocket и в обратном вызове сохраняет ответ. Вторая функция удаляет все сохраненные ответы. Вызов функций в порядке не гарантирует пустой список сообщений. При выполнении асинхронного программирования важно учитывать все возможные порядковые номера событий.
EDIT: здесь приведен пример кода
var messages = [];
...
io.sockets.on('connection', function (socket) {
socket.emit('ask', { question: 'How many fish do you have?' });
socket.on('reply', function (data) {
messages.push(data);
});
...
wipe();
});
function wipe() {
setTimeout(function() {
messages = [];
}, 500);
}
Ответ 4
Условия гонки могут все еще происходить, поскольку они действительно не имеют ничего общего с потоками, но делают предположения о времени и последовательности событий, поэтому потоки являются лишь примером этого.
Node.js является однопоточным, но все же одновременным, и условия гонки возможны. Например:
var http = require('http');
var size;
http.createServer(function (req, res) {
size = 0;
req.on('data', function (data) {
size += data.length;
});
req.on('end', function () {
res.end(size.toString());
})
}).listen(1337, '127.0.0.1');
Эта программа должна отправлять клиентам размер своего запроса. Если вы протестируете его, он будет работать правильно. Но на самом деле это основано на неявном предположении, что между начальным и конечным событиями запроса ничего не происходит. Если есть 2 или более одновременных клиентов, это не сработает.
Это происходит здесь, потому что переменная size
является общей, что очень похоже на то, когда два потока совместно используют переменную. Вы можете думать об абстрактном "асинхронном контексте", который очень похож на поток, но его можно приостановить только в определенные моменты.
Ответ 5
Да. Он может.
Условие гонки в Nodejs возможно, если вы используете модуль cluster
для инициализации нескольких сотрудников.
Случай
var cluster = require('cluster');
var fs = require('fs');
if(cluster.isMaster){
for(var i=0;i<4;i++){
cluster.fork();
}
}else{
fs.watch('/path/to/file',function(){
var anotherFile = '/path/to/anotherFile';
fs.readFile(anotherFile,function(er,data){
if(er){
throw er;
}
data = +data+1;
fs.writeFile(anotherFile,data,function(er){
if(er){
throw er;
}
fs.readFile(anotherFile,function(er,newData){
if(er){
throw er;
}
console.log(newData); //newData is now undetermined
});
});
});
});
}
Всякий раз, когда вы меняете наблюдаемый файл, 4 рабочих будут выполнять обработчик одновременно. Такое поведение вызывает неопределенный newData
.
Решение
if(cluster.isMaster){
var lock = {};
var timer = setInterval(function(){
if(Object.keys(cluster.workers).length >= 4){
return clearInterval(timer);
}
//note that this lock won't 100% work if workers are forked at the same time with loop.
cluster.fork().on('message',function(id){
var isLocked = lock[id];
if(isLocked){
return console.log('This task has already been handled');
}
lock[id] = 1;
this.send('No one has done it yet');
});
},100);
}else{
process.on('message',function(){
//only one worker can execute this task
fs.watch('/path/to/file',function(){
var anotherFile = '/path/to/anotherFile';
fs.readFile(anotherFile,function(er,data){
if(er){
throw er;
}
data = +data+1;
fs.writeFile(anotherFile,data,function(er){
if(er){
throw er;
}
fs.readFile(anotherFile,function(er,newData){
if(er){
throw er;
}
console.log(newData); //newData is now determined
});
});
});
});
});
//ask the master for permission
process.send('watch');
}
Ответ 6
Да, условия гонки (в смысле общего ресурса, имеющего несогласованную ценность из-за порядка событий) все еще могут произойти где угодно, где есть точка подвески, которая может привести к запуску другого кода (с нитями в любой строке), возьмите, например, эту часть асинхронного кода, которая полностью одинарная:
var accountBalance = 0;
async function getAccountBalance() {
// Suppose this was asynchronously from a database or something
return accountBalance;
};
async function setAccountBalance(value) {
// Suppose this was asynchronously from a database or something
accountBalance = value;
};
async function increment(value, incr) {
return value + incr;
};
async function add$50() {
var balance, newBalance;
balance = await getAccountBalance();
newBalance = await increment(balance, 50);
await setAccountBalance(newBalance);
};
async function main() {
var transaction1, transaction2;
transaction1 = add$50();
transaction2 = add$50();
await transaction1;
await transaction2;
console.log('$' + await getAccountBalance());
// Can print either $50 or $100
// which it prints is dependent on what order
// things arrived on the message queue, for this very simple
// dummy implementation it actually prints $50 because
// all values are added to the message queue immediately
// so it actually alternates between the two async functions
};
main();
Этот код имеет точки подвески в каждом отдельном ожидании и, как таковой, может переключаться контекст между двумя функциями в плохое время, производя "50 долларов", а не ожидаемым "100 долларов", это по сути тот же пример, что и пример Википедии для условий гонки в потоках, но с явными точками приостановки/повторного входа.
Как нитки, хотя вы можете решить такие условия гонки такими вещами, как Lock (aka mutex). Таким образом, мы могли бы предотвратить вышеупомянутое состояние гонки так же, как потоки:
var accountBalance = 0;
class Lock {
constructor() {
this._locked = false;
this._waiting = [];
}
lock() {
var unlock = () => {
var nextResolve;
if (this._waiting.length > 0) {
nextResolve = this._waiting.pop(0);
nextResolve(unlock);
};
if (this._waiting.length === 0) {
this._locked = false;
}
};
if (this._locked) {
return new Promise((resolve) => {
this._waiting.push(resolve);
});
} else {
this._locked = true;
return new Promise((resolve) => {
resolve(unlock);
});
}
}
}
var account = new Lock();
async function getAccountBalance() {
// Suppose this was asynchronously from a database or something
return accountBalance;
};
async function setAccountBalance(value) {
// Suppose this was asynchronously from a database or something
accountBalance = value;
};
async function increment(value, incr) {
return value + incr;
};
async function add$50() {
var unlock, balance, newBalance;
unlock = await account.lock();
balance = await getAccountBalance();
newBalance = await increment(balance, 50);
await setAccountBalance(newBalance);
await unlock();
};
async function main() {
var transaction1, transaction2;
transaction1 = add$50();
transaction2 = add$50();
await transaction1;
await transaction2;
console.log('$' + await getAccountBalance()); // Now will always be $100 regardless
};
main();
Ответ 7
У вас может возникнуть проблема, если вы объявите переменные внутри функций как имплицирующие глобальные var вместо локального var закрытия.
В этих двух ответах (один ответ и один ответ) вы можете найти несколько примеров о проблеме, которую вы не хотите встретить:
https://stackoverflow.com/info/25229739/race-condition-and-common-mistakes
Использовать параметр в обратных вызовах из функции, которая вызвала его