根据我的描述,当不同的线程尝试更改共享变量时会出现竞争条件,这可能导致这些线程的任何串行执行顺序都无法实现的值.
但是node.js中的代码在一个线程中运行,那么,这是否意味着在node.js中编写的代码没有竞争条件?
是.一旦开始共享资源,Node.js就会遇到竞争条件.
我错误地认为你无法在Node.js中获得竞争条件,因为它是单线程性质的,但只要你在节点之外使用共享资源(例如文件系统中的文件),你就会陷入竞争状态.当我试图理解这个时,我在这个问题中发布了这个问题的一个例子:node.js readfile问题
Node.js与其他环境的不同之处在于,您只有一个JavaScript执行线程,因此只有一个JavaScript实例运行您的代码(与线程环境相反,在线程环境中有许多线程同时执行您的应用程序代码时间.)
不,这是真的,你不能在单线程,非I/O执行程序上有竞争条件.
但是node.js主要是因为它的非阻塞编程方式.非阻塞意味着将侦听器设置为响应事件,您可以在等待此响应时执行其他操作.
为什么?因为获取响应的工作是在另一个线程上完成的.数据库,文件系统,在其他线程上运行,客户端显然在另一台计算机上运行,程序工作流程可以依赖于它的响应.
严格来说,node.js在一个线程上运行,但是你的程序工作流程包括I/O(数据库,文件系统),客户端和所有东西,在许多线程上运行.
因此,如果您向数据库添加内容的请求仍然存在竞争条件,然后只是发送删除请求而不等待第一个请求的响应.如果数据库与node.js在同一个线程中运行,则不存在竞争条件,并且请求只是立即执行的函数调用.
当您使用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'); }
没有.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); }
是的,竞争条件(在由于事件顺序而具有不一致值的共享资源的意义上)仍然可以发生在任何可能导致其他代码运行的悬浮点的任何地方(线程在任何行上),采取例如,这段完全是单线程的异步代码:
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();
竞争条件仍然可能发生,因为它们实际上与线程无关,而是在对事件时序和序列做出假设时,因此线程只是一个例子.
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
变量是共享的,就像两个线程共享变量时一样.你可以考虑一个抽象的"异步上下文",这很像线程,但它只能在某些点暂停.