The most unpleasant thing about nodejs is its single-threaded nature. It cannot do many things, and its performance is not strong enough for CPU-intensive scenarios. For a long time, I have wanted to find some solutions under the JavaScript language framework to solve the problems of inability to operate threads and poor performance. The solution that impressed me the most was fibers, but regardless of fibers or other solutions, thread operation is still very awkward. It relies too much on auxiliary threads and puts the cart before the horse; as far as fibers are concerned, JavaScript is inherently The low performance problem cannot be solved; the most awkward thing is that under the JavaScript language framework, message passing between threads is often very limited, and objects cannot be truly shared.
The addon method of nodejs is undoubtedly excellent, with extremely strong flexibility, complete functions and the performance of native code. Simply put, it allows nodejs to directly call the c/c module, which is a mixed development mode of javascript and native. It’s a good thing, why not use it? Addon should be considered a big topic. I don’t want to talk about it too deeply today. I don’t have much practice myself. Then implement a sleep function and think of it as an introduction.
sleep
Why can’t JavaScript implement real sleep? The sleep method registers a signal with the operating system kernel and sends a wake-up signal after a specified time, while the thread itself hangs. Essentially, when the thread sleep(1000) means telling the operating system: Do not allocate CPU time to me within 1000ms. Therefore, sleep can ensure that the thread no longer takes up CPU resources when it is suspended. JavaScript runs in a single thread and cancels the concept of threads. Naturally, there is no way to suspend and interrupt the main thread.
Some people will also try to use javascript to implement sleep, for example:
This uses an empty loop to block the running of the main process to implement sleep, which is obviously far from real sleep.
So what if we implement a real sleep?
Environment preparation
Development environment
Some of my blogs have said it before, so I will omit it here: node.js npm, python 2.7, visual studio/x-code.
Compilation Tools
The compilation tool needs to use node-gyp. Newer versions of nodejs come with this library. If node-gyp does not come with it, please execute:
I don’t have the energy to study the features of gyp. If you are familiar with other compilers such as gcc, it is not ruled out that gyp will have incompatibilities, and the compilation options and switches are also different. It is recommended to rewrite the C code for nodejs. If there are indeed modules that need to be reused, you can consider using the familiar gcc to compile it into a dynamic link library, then write a small amount of code to use the dynamic link library, and then use gyp to compile this part of the code for nodejs use.
Enter the project folder and execute npm init to initialize the project. In order to let nodejs know that we want to make an addon, we need to add in package.json:
If you have used gcc, then you must remember makefile. Similarly, gyp also describes the compilation configuration through a file. This file is binding.gyp, which is a json file that we are very familiar with. gyp is not the focus of our discussion, so binding.gyp will not be explored in depth. We will only focus on the most important configuration items. Here is an example of a simple but complete binding.gyp file:
Just take a look at the three configuration items involved:
1.target_name: Indicates the output module name.
2.sources: Indicates the source code path that needs to be compiled. This is an array.
3.include_dirs: Indicates the directories to be used during the compilation process. The header files in these directories can be searched in the precompilation directive #include. A rather special way of writing is used here. Instead of giving the path as a string constant, we run a command node -e "require('nan')". We will talk about the nan library later. First, let's take a look at what this command outputs: node_modulesnan, it turns out that this command means to return the path of the nan library.
C encoding
OK, now that the source code is hello.cc has been configured, create such a file. There is a problem that needs to be reminded in advance. The c module we write will eventually be used by the v8 engine, so the API, writing method, etc. are restricted by the v8 engine. Different versions of nodejs actually use different versions of the v8 engine, which means that it is difficult to use a set of c code to satisfy different versions of nodejs (referring to the compilation process. After the compilation is completed, it should be able to be used across versions, without verification. Yes. Github cannot upload binary libraries, so open source on Github will cause trouble. npm can directly upload binary libraries and skip the compilation step, so the problem is relatively minor).
node 0.11 and above:
using namespace v8;
void SleepFunc(const v8::FunctionCallbackInfo
Isolate* isolate = Isolate::GetCurrent();
HandleScope scope(isolate);
double arg0 = args[0] -> NumberValue();
Sleep(arg0);
}
void Init(Handle
NODE_MODULE(hello, Init);
node 0.10 and below:
using namespace v8;
Handle
HandleScope scope;
double arg0 = args[0] -> NumberValue();
Sleep(arg0);
Return scope.Close(Undefined());
}
void Init(Handle
NODE_MODULE(hello, Init);
It can be seen that the changes are still quite large. It would be great if these differences could be shielded. Is there any way? I am writing so much just to tell you that there is a way. It's time to ask out the nan library.
nan
Remember that in binding.gyp, we introduced the path of the nan library, which is to be used here. What is the nan library for? It provides a layer of abstraction that shields the syntax differences between nodejs 0.8, nodejs 0.10, nodejs 0.12, and addon before io.js. praise!
Install first: npm install --save nan and see how the same function is implemented after using nan:
NAN_METHOD(Sleep){
NanScope();
Double arg0=args[0]->NumberValue();
Sleep(arg0);
NanReturnUndefined();
}
void Init(Handle
NODE_MODULE(hello, Init);
What you need to know is the nan set. As for the v8 set, you don’t need to pay attention to it.
Looking from bottom to top:
This sentence defines the entrance of addon. Note that the first parameter must be consistent with our target_name in binding.gyp. The second parameter is the entry function of addon.
This code is the entry method of addon. It receives two parameters, namely exports and module. The above example omits the second parameter. If the module provides an object, you can directly specify the key-value to be provided to exports like the one in the example; if it is special and only provides a value or a function, you need to use the second parameter, similar to NODE_SET_METHOD(module , "exports", foo); . In this example, it means to output such a module:
Sleep is a function. Let’s take a look at the definition of Sleep:
In fact, it is to read the parameters passed in by javascript, convert them into double type, and then call the sleep method of c.
Compile addon
Now we will start compiling this module. First execute node-gyp configure to prepare for the build. It will generate a build folder and some files. Next run node-gyp build to start compilation. In this example, a hello.node file will eventually be generated in the /build/Release/ directory, which is the addon module that can eventually be referenced by javascript.
If there are subsequent modifications to the c code, there is no need to run node-gyp configure, just run node-gyp build directly.
nodejs use
Create an index.js and see how to use this module:
console.log(new Date);
sleep(1000);
console.log(new Date);
// result
// Wed Mar 04 2015 14:55:18 GMT 0800 (China Standard Time)
// Wed Mar 04 2015 14:55:19 GMT 0800 (China Standard Time)
매우 쉽습니다. 일반적인 자바스크립트 기능을 사용하는 것과 똑같습니다.
이제 이 글에서 공유하고 싶은 기술적 포인트를 설명했습니다. 그런데...처음에 제시된 방법과 어떻게 다른가요? 스크린샷을 찍지 않고 결과를 직접 설명하겠습니다.
애드온 방식은 Thread Suspension을 사용하기 때문에 이론적으로는 CPU 사용량과 메모리 변화가 없으며, 결과에서도 이를 확인시켜 줍니다. JavaScript 루프가 절전 모드를 시뮬레이션하는 방식을 살펴보겠습니다. 루프가 항상 실행되므로 메모리가 약간 증가하는 것은 이해할 수 있습니다. 25%의 CPU 사용량을 보면 상당히 수용 가능한 것으로 보입니다. 이것이 정말로 사실입니까? 진실이 밝혀질 때가 왔습니다. 제가 테스트한 노트북의 CPU는 듀얼 코어 및 4스레드인데 CPU 사용량이 25%인데...듀얼 코어 및 4스레드 스레드 중 하나가 이 절전 모드를 차지할 수 있을까요? 사실 이 기간 동안 단 하나의 스레드도 잠겨 있지 않은 것을 발견했는데 이는 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%까지 올라가고 코어 하나가 중단될 것으로 예측할 수 있습니다. 싱글코어 CPU로 처리하면 CPU가 갑자기 100%까지 올라갑니다.
CPU 부분은 좀 말이 많은 것 같고, 하이퍼스레딩 부분은 추측일 뿐입니다.