根据我的描述,当不同的线程尝试更改共享变量时会出现竞争条件,这可能导致这些线程执行的任何串行顺序都无法实现的值。
但是node.js中的代码在一个线程中运行,那么,这是否意味着在node.js中编写的代码没有竞争条件?
答案 0 :(得分:17)
没有。这是真的,你不能在单个线程,非I / O做程序上有竞争条件。
但node.js主要是因为它的非阻塞编程方式而快。非阻塞意味着将侦听器设置为响应事件,您可以在等待此响应时执行其他操作。
为什么? 因为获取响应的工作是在另一个线程上完成的。数据库,文件系统,在其他线程上运行,客户端显然在另一台计算机上运行,而您的程序工作流程可能依赖于 它的回应。
严格来说,node.js在一个线程上运行,但是你的程序工作流程包括I / O(数据库,文件系统),客户端和所有东西,在许多线程上运行。
因此,如果您向数据库添加内容的请求仍然存在竞争条件,然后只是发送删除请求而不等待第一个请求的响应。 如果数据库与node.js在同一个线程中运行,则不存在竞争条件,请求只是立即执行的函数调用。
答案 1 :(得分:17)
是。一旦开始共享资源,Node.js就会遇到竞争条件。
我错误地认为你无法在Node.js中获得竞争条件,因为它是单线程性质的,但只要你在节点之外使用共享资源(例如文件系统中的文件),你就可以进入竞争条件。当我试图理解这个问题时,我在这个问题中发布了这个问题的一个例子:node.js readfile woes
Node.js与其他环境的不同之处在于,您只有一个JavaScript执行线程,因此只有一个JavaScript实例运行您的代码(与执行应用程序代码的线程很多的线程环境相反)在同一时间。)
答案 2 :(得分:13)
是,竞争条件(在共享资源意义上由于事件顺序而具有不一致的价值)仍然可能发生在任何可能导致其他的暂停点的地方正在运行的代码(其中的线程位于任何行),例如这段完全是单线程的异步代码:
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",这与维基百科在线程中的竞争条件的示例基本相同,但具有明确的暂停/重新进入点。
就像线程一样,你可以用锁(又称互斥锁)之类的东西来解决这种竞争条件。所以我们可以像线程一样阻止上述竞争条件:
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);
} else {
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();
答案 3 :(得分:7)
没有。 Node.js没有上下文切换引起的竞争条件;但是,您仍然可以编写node.js程序,其中异步事件以意外顺序发生,导致状态不一致。
例如,假设您有两个功能。第一个通过WebSocket发送消息,并在回调中保存回复。第二个功能删除所有已保存的回复。按顺序调用函数不保证空消息列表。在进行异步编程时,考虑所有可能的事件排序非常重要。
编辑:这是一些示例代码
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 :(得分:6)
竞争条件仍然可能发生,因为它们与线程无关,而是在对事件时序和序列做出假设时,因此线程就是一个例子。
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');
该程序应该向客户发送其请求的大小。如果你测试它,似乎工作正确。但它实际上是基于隐式假设,即请求开始和结束事件之间没有任何反应。如果有两个或更多并发客户端则不起作用。
这发生在这里因为size
变量是共享的,就像两个线程共享一个变量一样。你可以考虑一个抽象的“异步上下文”,这很像线程,但它只能在某些点暂停。
答案 5 :(得分:3)
当您使用cluster
模块初始化多个工作人员时,Nodejs中的竞争条件是可行的。
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');
}