服務(wù)器
當(dāng)我們從一個(gè)前端或者后端走向全棧的時(shí)候,都會(huì)遇到一個(gè)問題:我人生中的第一臺(tái)云主機(jī)(服務(wù)器)應(yīng)該買什么配置的惫搏。
是不是cpu核心越多曙旭,內(nèi)存越大,硬盤容量越高越好晶府?桂躏?
相信大多數(shù)同學(xué)都有這個(gè)疑惑,一定是越貴的主機(jī)川陆,性能就越好嗎剂习?理論上來(lái)說(shuō),是這樣的,但是這并不代表你就應(yīng)該多花那些冤枉錢—因?yàn)閚odejs是單線程的鳞绕,你想一個(gè)進(jìn)程失仁,一個(gè)線程的程序(就算它到了i/o環(huán)節(jié)最多也就是4個(gè)一組去執(zhí)行)它對(duì)資源的消耗是很小的。如果你的項(xiàng)目比較簡(jiǎn)單们何,簡(jiǎn)單到根本不需要調(diào)用第二個(gè)進(jìn)程去干這件事兒萄焦,那么單核cpu就夠了。
我什么時(shí)候需要多線程
當(dāng)你的項(xiàng)目非常龐大的時(shí)候冤竹,龐大到什么程度拂封,舉個(gè)例子,比如用戶登錄和用戶在系統(tǒng)中修改某個(gè)數(shù)據(jù)鹦蠕。
這兩個(gè)操作冒签,沒有任何直接關(guān)聯(lián),甚至他們發(fā)生的時(shí)間段都不同钟病,比如用戶萧恕,10點(diǎn)登錄了,12點(diǎn)修改了某個(gè)數(shù)據(jù)肠阱。此時(shí)票唆,這兩個(gè)操作沒有直接關(guān)系,但是屹徘,這兩個(gè)操作都需要消耗很多的系統(tǒng)資源—比如幾十萬(wàn)個(gè)用戶在頻繁的登錄..此時(shí)走趋,就有必要給這兩個(gè)任務(wù)區(qū)分進(jìn)程去執(zhí)行了!
分開兩個(gè)進(jìn)程可以有效的增加系統(tǒng)資源利用率缘回。后面我們會(huì)講這是為什么吆视。
這里你只需要了解,一個(gè)cpu酥宴,一次只能處理一個(gè)進(jìn)程啦吧,但是如果有兩個(gè)cpu,他可以同時(shí)處理兩個(gè)進(jìn)程拙寡,分進(jìn)程就好處就體現(xiàn)出來(lái)了授滓。那么,什么是線程肆糕,什么是進(jìn)程呢般堆?
什么是線程,什么是進(jìn)程
說(shuō)實(shí)話诚啃,看到這個(gè)東西淮摔,絕大多數(shù)人是懵逼的,就連我認(rèn)識(shí)的一些從業(yè)幾年的同事始赎,一時(shí)半會(huì)也解釋不清和橙。
今天龍哥將會(huì)用最簡(jiǎn)單的幾句話為你講明白:首先仔燕,在講這兩個(gè)東西之前,我們要了解電腦是怎么運(yùn)作的...
杠精馬上反問:線程跟進(jìn)程魔招,你扯什么計(jì)算機(jī)原理拔蟆?
別急办斑,因?yàn)榫€程和進(jìn)程就是基于這個(gè)來(lái)的外恕。
我們都知道,組成一臺(tái)計(jì)算機(jī)(這里指主機(jī)乡翅,不包括顯示器鳞疲,鍵盤等外設(shè))。最核心的就是這幾個(gè)東西峦朗,cpu建丧,內(nèi)存排龄,硬盤波势,主板。
那么橄维,你知道它的工作流程是什么樣的嗎尺铣?其實(shí)你會(huì)發(fā)現(xiàn),這里面除了cpu是用來(lái)計(jì)算數(shù)據(jù)的争舞,其他的東西都只干一件事凛忿,那就是儲(chǔ)存。
無(wú)論是哪個(gè)硬件發(fā)送來(lái)一條指令竞川,這些指令都會(huì)形成一個(gè)隊(duì)列店溢,然后一個(gè)接一個(gè)發(fā)送都cpu那里去處理,放心委乌,cpu的執(zhí)行效率高的驚人床牧,驚人到一個(gè)一個(gè)處理,會(huì)讓人誤以為是并行完成的遭贸。
而這個(gè)調(diào)用cpu轉(zhuǎn)一圈出來(lái)的流程戈咳,人們稱之為:流。
然后你該說(shuō)了壕吹, 這跟進(jìn)程和線程有什么關(guān)系??
所謂的進(jìn)程著蛙,就是一個(gè)運(yùn)行中的程序,比如耳贬,我們開了一個(gè)QQ踏堡,打開任務(wù)管理器,你看到這個(gè)QQ就是一個(gè)進(jìn)程咒劲。
注意顷蟆,這里就很關(guān)鍵了胖秒,我們前面說(shuō),cpu一次只能處理一個(gè)指令慕的,而這個(gè)指令就是進(jìn)程阎肝。
然后,當(dāng)這個(gè)進(jìn)程不活動(dòng)的時(shí)候肮街,它的數(shù)據(jù)就被儲(chǔ)存在內(nèi)存中风题,內(nèi)存也叫這個(gè)程序的暫存器。
好嫉父,先別蒙沛硅,那我們簡(jiǎn)單來(lái)說(shuō),一個(gè)程序就是一個(gè)進(jìn)程绕辖。那nodejs就似乎一個(gè)進(jìn)程了唄摇肌?對(duì),完全正確仪际,我們運(yùn)行的環(huán)境围小,包括mysql,redis這些程序都是獨(dú)自占一個(gè)進(jìn)程树碱。
行肯适,那么線程呢,老有人說(shuō)成榜,多線程框舔,多線程有什么用?赎婚?
單線程和多線程
首先我得告訴你刘绣,nodejs是單線程的。所謂線程挣输,就是在一個(gè)程序(進(jìn)程)中纬凤,任務(wù)是怎么處理的。
如果是多線程歧焦,就是多個(gè)任務(wù)一起執(zhí)行移斩,單線程,就是一個(gè)一個(gè)執(zhí)行绢馍。
但是向瓷,前面我們說(shuō)了。最后處理這個(gè)任務(wù)的舰涌,并不是內(nèi)存猖任,而是cpu啊。cpu一次只能執(zhí)行一個(gè)任務(wù)瓷耙。所以多個(gè)進(jìn)程朱躺,多個(gè)線程刁赖,只是看上去是并行的,其實(shí)最后還是一個(gè)一個(gè)執(zhí)行长搀。
所以宇弛,多個(gè)核cpu就是為了解決這個(gè)問題而存在的。源请。理論上來(lái)說(shuō)枪芒,你的硬件越牛逼,cpu核數(shù)越多谁尸,你調(diào)用多線程或者多進(jìn)程的收益也就越大舅踪。但是,花的錢也就越多良蛮。抽碌。
區(qū)別
那照你這么說(shuō),他倆就沒什么區(qū)別了唄决瞳。有區(qū)別货徙,進(jìn)程里面是包含線程的,所以瞒斩,當(dāng)你需要多個(gè)cpu同時(shí)處理的時(shí)候破婆,需要建立多個(gè)進(jìn)程涮总,當(dāng)需要一個(gè)進(jìn)程里面的任務(wù)胸囱,分別運(yùn)行的時(shí)候,需要多個(gè)進(jìn)程瀑梗。
好我講完了烹笔。那么你發(fā)現(xiàn)問題了嗎?
nodejs是單線程的抛丽,我說(shuō)了兩遍了谤职。如果是這樣,那么它豈不是在cpu使用上亿鲜,不如java等可以多進(jìn)程的語(yǔ)言了嗎允蜈??
nodejs
還真不是蒿柳,nodejs只是看上去是單線程的饶套。因?yàn)閖s是瀏覽器語(yǔ)言所以它必須是單線程。但是垒探,真到了執(zhí)行的步驟妓蛮,你可管不了它怎么運(yùn)作。
引用博客園牛人的解釋:
node.js采用單線程異步非阻塞模式圾叼,也就是說(shuō)每一個(gè)計(jì)算獨(dú)占cpu蛤克,遇到I/O請(qǐng)求不阻塞后面的計(jì)算捺癞,當(dāng)I/O完成后,以事件的方式通知构挤,繼續(xù)執(zhí)行計(jì)算2髓介。
但nodejs真的是單線程嗎?其實(shí)只有js執(zhí)行是單線程筋现,I/O顯然是其它線程版保。js執(zhí)行線程是單線程,把需要做的I/O交給libuv夫否,自己馬上返回做別的事情彻犁,然后libuv在指定的時(shí)刻回調(diào)就行了。其實(shí)簡(jiǎn)化的流程就是醬紫的凰慈!細(xì)化一點(diǎn)汞幢,nodejs會(huì)先從js代碼通過node-bindings調(diào)用到C/C++代碼,然后通過C/C++代碼封裝一個(gè)叫 “請(qǐng)求對(duì)象” 的東西交給libuv微谓,這個(gè)請(qǐng)求對(duì)象里面無(wú)非就是需要執(zhí)行的功能+回調(diào)之類的東西森篷,給libuv執(zhí)行以及執(zhí)行完實(shí)現(xiàn)回調(diào)。
你可以發(fā)現(xiàn)豺型,其實(shí)nodejs在執(zhí)行的時(shí)候仲智,并不是一個(gè)挨著一個(gè)去執(zhí)行,而是姻氨,以一個(gè)隊(duì)列的形式钓辆,封裝一個(gè)任務(wù)就回來(lái)繼續(xù)回來(lái)往下走,而那個(gè)封裝的任務(wù)是交給底層的線程池去完成的肴焊。這是最騷的前联。
其次,nodejs可以開啟子進(jìn)程娶眷,雖然也是多線程似嗤,但是它提供了一個(gè)主從關(guān)系。(你只需要理解nodejs也可以才用多線程模式運(yùn)行就行了)所以届宠,nodejs用來(lái)處理高并發(fā)是完全不在話下的烁落。
好了,最后上幾個(gè)例子大家體驗(yàn)一下吧豌注。
同步進(jìn)程:
var fs=require("fs");
function foo(){
function beginAnotherTask(){
var file=fs.createReadStream("./yishengyouni.mp3");
file.on("data",function(data){
console.log("讀取到%d字節(jié)伤塌。",data.length)
});
process.nextTick(beginAnotherTask);
}
}
var file=fs.createReadStream("./yishengyouni.mp3");
file.on("data",function(data){
console.log("讀取到%d字節(jié)。",data.length)
});
foo();
使用next.Tick方法可以講一個(gè)任務(wù)幌羞,推遲到下一個(gè)異步或者同步方法的執(zhí)行時(shí)寸谜。
多進(jìn)程
使用child_process的spawn方法開啟多個(gè)進(jìn)程
并通過spawn中的stdio屬性來(lái)關(guān)聯(lián)兩個(gè)進(jìn)程(管道)
var cp=require("child_process");
var sp1=cp.spawn("node",["test1.js","one","tow","three","four"],{cwd:"./test"});
var sp2=cp.spawn("node",["test2.js"],{stdio:"pipe"});
sp1.stdout.on("data",function(data){
console.log("子進(jìn)程標(biāo)準(zhǔn)輸出:"+data);
sp2.stdin.write(data);
});
sp1.on("exit",function(code,signal){
console.log("子進(jìn)程退出:"+code);
process.exit();
});
sp1.on("error",function(err){
console.log("子進(jìn)程開啟失敗:"+err);
process.exit();
})
process.stdout.write("子進(jìn)程當(dāng)前工作目錄為:"+process.cwd());
process.argv.forEach(function(val,index,array){
process.stdout.write("\r\n"+index+":"+val);
})
var fs=require("fs");
var out=fs.createWriteStream("./message.txt");
process.stdin.on("data",function(data){
out.write(data);
})
process.stdin.on("end",function(data){
process.exit();
})
注意:目錄的結(jié)構(gòu)
test2是在test文件夾外面的。