首頁 > web前端 > js教程 > nodejs中實作sleep功能實例_node.js

nodejs中實作sleep功能實例_node.js

WBOY
發布: 2016-05-16 16:07:48
原創
3212 人瀏覽過

nodejs最讓人不爽的就是其單線程特性,很多事情沒法做,對CPU密集型的場景,性能也不夠強勁。很長一段時間,我想在javascript語言框架下尋求一些解決方案,解決無法操作執行緒、效能差的問題。曾經最讓我印象深刻的方案是fibers,不過fibers也好,其他方案也好,在線程操作上還是很彆扭,太過依賴輔助線程,本末倒置;就fiber而言,javascript固有的低效能問題並不能解決;最彆扭的是在javascript語言框架下,線程間的消息傳遞常常很受限制,經常無法真正地共享物件。

nodejs的addon方式無疑是極好的,具有極強的靈活性、完備的功能和原生程式碼的效能。簡單說就是讓nodejs直接呼叫c/c 模組,是一種javascript和native的混合開發模式。好東西呀,為什麼不用呢? addon應該算是一個大話題,今天我也不想太深入說這個,我自己的實踐也不是很多。那就實作一個sleep函數,就當是拋磚引玉吧。

sleep

為什麼javascript實作不了真正的sleep? sleep方法是透過向作業系統核心註冊一個訊號,指定時間後發送喚醒訊號,而執行緒本身則掛起。本質上當線程 sleep(1000) 代表告訴作業系統:1000ms內不要給我分配CPU時間。所以sleep能保證在執行緒掛起時不再佔用CPU資源。而javascript是單執行緒運行,本身取消了執行緒的概念,自然沒有辦法將主執行緒掛起中斷。

也有人會嘗試用javascript方法要實作sleep,例如這樣:

複製程式碼 程式碼如下:

function sleep(sleepTime) {
    for(var start = new Date; new Date - start }

這是採用空迴圈阻塞住主進程的運行來實現sleep,明顯跟真正的sleep相去甚遠。

那如果實作一個真正的sleep呢?

環境準備

開發環境

之前我的一些部落格已經說過,這裡從略:node.js npm、python 2.7、visual studio/ x-code。

 編譯工具

編譯工具需要採用node-gyp,較新版的nodejs自備此函式庫,如果沒有自備node-gyp,請執行:

複製程式碼 程式碼如下:

npm install -g node-gyp

 gyp特性我沒有精力去研究,如果你比較熟悉gcc等其他編譯器,不排除gyp會有不相容之處,而且編譯選項和開關也是不盡相同。建議針對nodejs重新編寫c 程式碼,如果確實有模組需要重複使用,可以考慮先用熟悉的gcc編譯成動態連結函式庫,再寫少量程式碼來使用動態連結函式庫,再把這部分程式碼用gyp編譯出來供nodejs使用。

進入專案資料夾,執行 npm init 初始化專案。為了讓nodejs知道我們想製作addon,我們需要在package.json中加入:

複製程式碼 程式碼如下:

"gyp-file": true

如果使用過gcc,那你一定記得makefile。類似的,gyp也是透過一個檔案來描述編譯配置,這個檔案為binding.gyp,它是一個我們非常熟悉的json檔案。 gyp不是我們探討的重點,所以binding.gyp也不會深入探究,我們只關注最重要的一些配置。以下是一份簡單但完整的binding.gyp檔案範例:

複製程式碼 程式碼如下:

{
  "targets": [
    {
      "target_name": "hello",
      "sources": [ "hello.cc" ],
      "include_dirs": [
        "       ]
    }
  ]
}


就看看這裡面涉及的三個配置:

1.target_name:表示輸出出來的模組名稱。
2.sources:表示需要編譯的原始碼路徑,這是一個陣列。
3.include_dirs:表示編譯過程中要使用的目錄,這些目錄中的頭檔可以在預先編譯指令 #include 搜尋。這裡使用了一個比較特殊的寫法,沒有把路徑用字串常數給出,而是運行一個命令node -e "require('nan')" ,nan庫後面再說,先看看這個命令輸出什麼: node_modulesnan ,原來這句指令的意思是回傳nan函式庫的路徑。

C 編碼

OK,既然已經配置了原始碼是hello.cc,那就建立一個這樣的檔案。有一個問題要事先提醒大家,我們寫的c 模組最後是要被v8引擎使用,所以api、寫法等受到v8引擎的限制。而不同版本的nodejs其實採用的v8引擎的版本也不盡相同,這也意味著很難用一套c 程式碼滿足不同版本的nodejs(指編譯過程,編譯完成後跨版本應該能夠使用,沒有驗證過。

node 0.11以上版本:

複製程式碼 程式碼如下:

#include
#include

using namespace v8;

void SleepFunc(const v8::FunctionCallbackInfo& args) {
  Isolate* isolate = Isolate::GetCurrent();
  HandleScope scope(isolate);
  double arg0 = args[0] -> NumberValue();
  Sleep(arg0);
}

void Init(Handle exports) {
  Isolate* isolate = Isolate::GetCurrent();
  exports->Set(String::NewFromUtf8(isolate, "sleep"),
      FunctionTemplate::New(isolate, SleepFunc)->GetFunction());
}

NODE_MODULE(hello, Init);

node 0.10以下版本:

複製程式碼 程式碼如下:

#include
#include

using namespace v8;

Handle SleepFun(const Arguments& args) {
  HandleScope scope; 
  double arg0 = args[0] -> NumberValue();
  Sleep(arg0);
  return scope.Close(Undefined());
}

void Init(Handle exports) {
  exports->Set(String::NewSymbol("sleep"),
      FunctionTemplate::New(SleepFun)->GetFunction());
}

NODE_MODULE(hello, Init);


可以看出,變化還是相當大的,如果能屏蔽這些差異就太好了,有辦法了?我寫這麼多還不就是想告訴你有辦法。是時候請出nan庫了。

nan

還記得在binding.gyp中,我們引入nan庫的路徑,就是要在這裡用。 nan庫是幹嘛的呢?它提供了一層抽象,屏蔽了nodejs 0.8、nodejs 0.10、nodejs 0.12、io.js之前addon的語法差異。讚!

先安裝: npm install --save nan ,看看同樣的功能,用了nan後如何實現:

複製程式碼 程式碼如下:

#include
using namespace v8;

NAN_METHOD(Sleep){
    NanScope();
    double arg0=args[0]->NumberValue();
    Sleep(arg0);
    NanReturnUndefined();
}

void Init(Handle exports){
    exports->Set(NanSymbol("sleep"), FunctionTemplate::New(Sleep)->GetFunction());
}

NODE_MODULE(hello, Init);

你需要了解的就是nan這套東西,至於v8的那一套就可以不用關注。

由下往上看:

複製程式碼 程式碼如下:

NODE_MODULE(hello, Init);

 這句定義addon的入口。注意第一個參數要與我們在binding.gyp中target_name一項一致。第二個參數就是addon的入口函數。
 

複製程式碼 程式碼如下:

 void Init(Handle exports){
    exports->Set(NanSymbol("sleep"), FunctionTemplate::New(Sleep)->GetFunction());
}
 

 這段程式碼就是addon的入口方法。它接收兩個參數,分別是exports和module。上面的範例省略了第二個參數。如果模組提供一個對象,可以像範例中那個,直接給exports指定要提供的key-value;如果特殊一點,只提供一個數值,或一個函數,則需要用到第二個參數,類似於NODE_SET_METHOD(module , "exports", foo); 。這個範例中是表示要輸出這樣一個模組:

複製程式碼 程式碼如下:

{
    "sleep": Sleep
}

Sleep是個函數,下來就來看看Sleep的定義:

複製程式碼 程式碼如下:

NAN_METHOD(Sleep){
    NanScope();
    double arg0=args[0]->NumberValue();
    Sleep(arg0);
    NanReturnUndefined();
}

其實就是讀取javascript傳入的參數,轉成double型,再呼叫c 的sleep方法。

編譯addon

下面就要開始編譯這個模組了。首先執行 node-gyp configure 來進行建置前準備工作,它會產生一個build資料夾和一些檔案。接下來執行 node-gyp build 就可以開始編譯了。在這個範例中,最終會在/build/Release/目錄下產生一個hello.node文件,這就是最終能被javascript引用的addon模組了。

如果後續對c 程式碼有修改,就不用再執行 node-gyp configure ,直接執行 node-gyp build 就好。

nodejs使用

建立一個index.js,看看怎麼用這個模組吧:

複製程式碼 程式碼如下:

var sleep=require('./build/Release/hello.node').sleep;

console.log(new Date);
sleep(1000);
console.log(new Date);

// result
// Wed Mar 04 2015 14:55:18 GMT 0800 (中國標準時間)
// Wed Mar 04 2015 14:55:19 GMT 0800 (中國標準時間)       

非常に簡単で、通常の JavaScript 関数を使用するのとまったく同じです。

この時点で、この記事で共有したい技術的なポイントは説明されました。しかし...最初に説明した方法とどう違うのでしょうか?スクリーンショットは撮りません。結果を直接説明します:

アドオン方法はスレッドの一時停止を使用するため、理論的には CPU 使用率やメモリの変更はなく、結果でもこれが検証されています。 JavaScript ループがスリープをシミュレートする方法を見てみましょう。ループは常に実行されているため、メモリが多少増加することは理解できますが、CPU 使用率が 25% であることを見ると、かなり許容範囲内であることがわかります。これは本当にそうなのでしょうか?真実が明らかになる時が来た。私がテストしたラップトップの CPU はデュアルコアで 4 スレッドですが、25% の CPU 使用率と組み合わせると、デュアルコアと 4 スレッドのスレッドの 1 つがこのスリープによって占有される可能性があります。実際、この期間中にロックされたスレッドは 1 つもありませんでした。しかし、これは JavaScript の結果ではなく、Intel Hyper-Threading の功績です。 4 スレッドと言っているので、本質的には 2 つの処理コアがデュアル スレッドにしかならないのですが、CPU はタイム スライス カットというちょっとしたトリックを実行します。たとえば、コア cpu01 が t0 と t2 に分割されているとします。n ティック (スケジューリング サイクル) 後のティックでタスクが t0 に割り当てられ、次のティックでタスクが t2 に割り当てられるとします。したがって、比較的長い時間スケール (スケジューリング期間と比較して) から見ると、t0 と t2 でのタスクの実行時間は基本的に同じになります。したがって、提示された状況は、nodejs プロセスが t0 または t2 を 100% 占有するのではなく、それぞれ約 50% を占めるということです。 Windows のプロセスのスケジューリングは比較的複雑であるため、CPU 使用率は大きく変動します。このスクリプトの処理にデュアルコアおよびデュアルスレッドの CPU を使用すると、CPU 使用率が 50% に上昇し、1 つのコアがスタックすることが予測できます。シングルコアCPUで処理するとCPUがいきなり100%まで上がってしまいます。

CPU セクションは少し話しすぎているようです。ハイパースレッディングセクションは単なる推測です。ちょっと見てください。

相關標籤:
來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板