10 Ocak 2018 Çarşamba

Node.js Dersleri 8 - Event Loop & setImmediate() vs. nextTick() vs. timeOut(callback,0) & NodeJS'de tanımladığımız bir fonksiyon %100 senkron veya %100 asenkron olmalıdır

24 - Event Loop & setImmediate() vs. nextTick() vs. timeOut(callback,0) & NodeJS'de tanımladığımız bir fonksiyon %100 senkron veya %100 asenkron olmalıdır(bkz:Explanation1)

Bir event loop'da, current tick'in kuyruğuna eklenmiş olan asenkron fonksiyonlar çalıştırılır.
Özet için HelloNode14 ve HelloNode13'e bak. Bu 3 method'un aldığı argument olan fonksiyon, sonraki event loop'da asenkron çalışır. Eğer bunları bir callback fonksiyonu içerisinde next tick'in queue'suna eklersek, önce process.nexttick(), sonra setImmediate(), en son ise timeout(callback,0) çalışır. Bunları bir callback fonksiyon içerisinde değil de modül scope'unda(örnek için HelloNode14'e bak) tanımlarsak hangisinin önce hangisinin sonra çalışacağı tamamen tesadüftür. Diğer ayrıntılar çok da önemli değil.
Explanation 1 :
From official doc :   https://nodejs.org/docs/latest/api/process.html#process_process_nexttick_callback_arg
http://stackoverflow.com/questions/31944806/are-callbacks-in-node-js-either-always-asynchronous-or-always-synchronous-or-ca
process.nextTick(callback[, arg][, ...])
  • callback <Function>
  • [, arg][, ...] <any> Additional arguments to pass when invoking the callback
The process.nextTick() method adds the callback to the "next tick queue". Once the current turn of the event loop turn runs to completion, all callbacks currently in the next tick queue will be called.
This is not a simple alias to setTimeout(fn, 0), it's much more efficient. It runs before any additional I/O events (including timers) fire in subsequent ticks of the event loop. (process.nexttick(), callback fonksiyou next tick'in kuyruğuna ekler. Current event loop tamamlanınca, sonraki event loop'a geçilir ve bu tick'in kuyruğundaki callback'ler çağırılır. Bir event queue'da en önce çalışan şey nexttick'dir, sonra setImmediate, en son timeout(callback,0)'dır. )
console.log('start');
process.nextTick(() => {
 console.log('nextTick callback');
});
console.log('scheduled');
// Output:
// start
// scheduled
// nextTick callback
This is important when developing APIs in order to give users the opportunity to assign event handlers after an object has been constructed but before any I/O has occurred:
function MyThing(options) {
 this.setupOptions(options);
 process.nextTick(() => {
   this.startDoingStuff();
 });
}
var thing = new MyThing();
thing.getReadyForStuff();  // önce burası çalışır. Çünkü bu kod senkron.
// sonra process.nexttick çalışır. Çünkü bu kod asenkron olup next event loop'da çalışacaktır.
// thing.startDoingStuff() gets called now, not before.
It is very important for APIs to be either 100% synchronous or 100% asynchronous. Consider this example:
// WARNING!  DO NOT USE!  BAD UNSAFE HAZARD!
function maybeSync(arg, cb) {
 if (arg) {
   cb();
   return;
 }
 fs.stat('file', cb);
}
This API is hazardous because in the following case:
maybeSync(true, () => {
 foo();
});
bar();
It's not clear whether foo() or bar() will be called first unless you look at the internals of the maybeSync() function. And even then you don't really know what'll happen at runtime unless you can guarantee that the values you pass it will always be of a certain type. You should be able to rely on a particular function always being synchronous or always asynchronous without having to look at how the function is implemented. ( Yukarıdaki case için maybeSync fonksiyonu senkron çalışır. Ancak maybeSync fonksiyonu, alacağı ilk parametrenin değerine göre senkron veya asenkron çalışabilme özelliğine sahiptir, bu şekilde bir fonksiyon tanımlamak çok tehlikelidir. Bir fonksiyon ya %100 senkron çalışmalı ya da %100 asenkron çalışmalıdır, yani fonksiyonun senkron çalışıp çalışmayacağı verdiğimiz input'a veya başka bir koşula göre belirlenmemelidir. Bir fonksiyonun implementation'ını bilmemize gerek kalmadan o fonksiyonun asenkron veya senkron çalıştığını bilebilmeliyiz.  )
The following approach is much better(Aşağıdaki gibi bir fonksiyon tanımlamak daha iyidir, çünkü bu fonksiyonun her koşul altında asenkron çalışacağından eminiz.) :
function definitelyAsync(arg, cb) {
 if (arg) {
   process.nextTick(cb);
   return;
 }
 fs.stat('file', cb);
}
Note: the next tick queue is completely drained on each pass of the event loop before additional I/O is processed. As a result, recursively setting nextTick callbacks will block any I/O from happening, just like a while(true); loop. ( nextTick() method'unu sürekli bir şekilde çağırırsak, event loop'da sürekli bu method çalışacağı için diğer asenkron fonksiyonlar çalışamayacak dolayısıyla event loop ilerleyemeyecektir. )
Note :
what is () => { ... } in the second parameter of maybeSync() function ?
They're called arrow functions, and are part of the new ES6 (EcmaScript 6). () => { ... } is the shorthand equivalent of function() { ... } - but beware there are also some differences in scope.
Explanation 2 :
What exactly is a Node.js  event loop tick?
Remember that while JavaScript is single-threaded, all of node's I/O and calls to native APIs are either asynchronous (using platform-specific mechanisms), or run on a separate thread. (This is all handled through libuv.) ( Javascript single-threaded olduğunu hatırlayalım, nodejs'deki tüm I/O 'lar native API'ları çağırmak asenkron'dur veya libuv aracılığıyla ayrı bir thread'de çalışır.)
So what we do is place the event on a queue in a thread-safe manner. In oversimplified psuedocode, something like: (Asenkron fonksiyonlar çağırıldıklarında arka planda bir event kuyruğuna girerler. Şöyle düşün : thread'i lock edip event kuyruğuna eklenirler, sonra thread unlock edilir gibi düşün. Basitçe aşağıdaki kod gibidir yani. )
lock (queue) {
   queue.push(event);
}
Back on the main JavaScript thread (but on the C side of things), we do something like: ( Jacascript'deki single thread'de ise arka planda şöyle bir şey olur kabaca anlatmak gerekirse. While'daki herbir döngü bir tick'e karşılık gelir. Kuyruk kilitlenir, böylece kuyruğa yeni event alınması kapatılır. Sonra bu kuyruk tickEvents isimli bir array'e kopyalanır. Kuyruğun kilidi açılır. Sonra tickEvents array'indeki tüm event'ler yani callback fonksiyonlar sırayla çağırılırlar. )
while (true) {
   // this is the beginning of a tick
   lock (queue) {
       var tickEvents = copy(queue); // copy the current queue items into thread-local memory
       queue.empty(); // ..and empty out the shared queue
   }
   for (var i = 0; i < tickEvents.length; i++) {
       InvokeJSFunction(tickEvents[i]);
   }
   // this the end of the tick
}
The while (true) (which doesn't actually exist in node's source code; this is purely illustrative) represents the event loop. The inner for invokes the JS function for each event that was on the queue.
( nodejs. source kodunda aslında böyle bir kod yoktur, bu pseudo koddur, mantığını anlatmak için gösterilmiştir. )
This is a tick: the synchronous invocation of zero or more callback functions associated with any external events. Once the queue is emptied out and the last function returns, the tick is over. We go back to the beginning (the next tick) and check for events that were added to the queue from other threads while our JavaScript was running. ( Bu kuyruk bir tick'dir. Bu kuyruktaki tüm callback fonksiyonları sırayla invoke edilirler. Kuyruktaki tüm callback fonksiyonlar çağırılınca kuyruk boşalmış ve tick bitmiş olur ve sonraki tick(next tick)'e geçilir, yani sonraki döngüye geçilir. Yani kuyruğu kopyalayıp bu kuyruktaki tüm callback fonksiyonlarını tek tek çağırırken diğer bir yandan boş kuyruğa yeni callback fonksiyonlar eklenir. Bu sırada Javascript durmadan çalışmaktadır. )
What can add things to the queue? (Aşağıdaki işlemler event kuyruğuna yeni şeyler, callback fonksiyonlar vs. eklerler: )
  • process.nextTick
  • setTimeout/setInterval
  • I/O (stuff from fsnet, and so forth)
  • any native modules that use the libuv work queue to make synchronous C/C++ library calls look asynchronous (libuv 'u kullanan native module'ler)

What is the Event Loop?
https://github.com/nodejs/node/blob/master/doc/topics/the-event-loop-timers-and-nexttick.md
The event loop is what allows Node.js to perform non-blocking I/O operations — despite the fact that JavaScript is single-threaded — by offloading operations to the system kernel whenever possible. ( Javascript single-threaded olmasına karşın, Node.js'nin non-blocking IO işlemlerini gerçekleştirebilmesini yani asenkron çalışabilmesini sağlayan mekanizma event loop'dur. Asenkron çalışacak işlemler, system kernel'a yüklenir.  )
Since most modern kernels are multi-threaded, they can handle multiple operations executing in the background. When one of these operations completes, the kernel tells Node.js so that the appropriate callback may added to the poll queue to eventually be executed. We'll explain this in further detail later in this topic. ( Modern kernel'ların çoğu multi-threaded'dır, arka planda birden fazla işi paralel olarak yapabilirler. Bu işlerden birisi bittiğinde, kernel Node.js'ye bunu söyler, böylece uygun callback fonksiyonu çalıştırılmak üzere poll queue'ya eklenir. Bunun detayına birazdan gireceğiz. )

Event Loop Explained
When Node.js starts, it initializes the event loop, processes the provided input script (or drops into the REPL, which is not covered in this document) which may make async API calls, schedule timers, or call  process.nextTick(), then begins processing the event loop. ( Node.js başladığında, event loop'u başlatır. Komut satırından verdiğimiz komuttaki script çalıştırılır, bu script dosyasından asenkron fonksiyonlar çağırılır, timer'lar schedule edilir, veya process.nextTick() method'u çağırılır. Sonra event loop'daki akış ilerler. )
( Kernel, bir yandan yeni event'leri yeni bir kuyruğa sokarken diğer yandan current kuyruktaki event'leri çalıştırarak current kuyruğu boşaltıp sonraki tick'e geçilmesini sağlar.)
A timer specifies the threshold after which a provided callback may be executed rather than the exact time a person wants it to be executed. Timers callbacks will run as early as they can be scheduled after the specified amount of time has passed; however, Operating System scheduling or the running of other callbacks may delay them. ( 100 ms sonra timeout olmasını istediğimiz bir callback fonksiyon tanımladık aşağıdaki örnekte. Ancak bu fonksiyon tam olarak 100 ms. sonra çalışmaya başlayacak diye bir şey diyemeyiz. 100ms'den sonra herhangi bir zamanda çalışmaya başlayacak deriz. Aşağıdaki örnekte callback fonksiyon'un setTimeout() ile schedule edilmesi ve çalışması arasında 105ms zaman geçmiştir. )
var fs = require('fs');
 // let's assume this takes 95ms to complete  
 fs.readFile('/path/to/file', function() {
  var startCallback = Date.now();
  // do something that will take 10ms...
  while (Date.now() - startCallback < 10) {
   ; // do nothing
  }
 });
setTimeout(function () {
 var delay = Date.now() - timeoutScheduled;
 console.log(delay + "ms have passed since I was scheduled");
}, 100);
( Bu örnekte readFile() method'unun çalışması 95 ms, bu method'un callback fonksiyonunun çalışması 10ms sürer. Dolayısıyla 95 ms sonra readFile method'unun çalışması bitince bu method'a verilen callback fonksiyonu çalışır. Bu callback fonksiyonu bitince 105 saniye geçmiştir. Sonra 100ms timeout ile belirlediğimiz fonksiyon çalışır. )
Event loop'da star
Note: To prevent starving of  the event loop, libuv also has a hard maximum (system dependent) before it stops polling for more events.

setImmediate() vs setTimeout()
setImmediate and setTimeout() are similar, but behave in different ways depending on when they are called.
  • setImmediate() is designed to execute a script once the current poll phase completes.
  • setTimeout() schedules a script to be run after a minimum threshold in ms has elapsed.
( setImmediate() ve setTimeout() fonksiyonları benzerdir, ancak ne zaman ve nerede çağırıldıklarına göre farklı şekilde çalışırlar. Örneğin bu fonksiyonları module scope'unda çalıştırırsak hangisinin önce hangisinin sonra çalışacağı hakkında kesin bir şey söyleyemeyiz, process'in işlemine göre bu değişebilir. Ancak  bir callback fonksiyonu içeresinde hem setImmediate hem setTimeout fonksiyonlarını çağırırsak, setImmediate fonksiyonunun daha önce çalışacağını kesin olarak söyleyebiliriz. )
The order in which the timers are executed will vary depending on the context in which they are called. If both are called from within the main module, then timing will be bound by the performance of the process (which can be impacted by other applications running on the machine).
For example, if we run the following script which is not within an I/O cycle (i.e. the main module), the order in which the two timers are executed is non-deterministic, as it is bound by the performance of the process:(Aşağıdaki örnekte module scope'u içerisinde çağırdık bu fonksiyonları, dolayısıyla hangisi önce hangisi sonra çalışacak konusunda kesin bir şey söyleyemeyiz. )
// timeout_vs_immediate.js
setTimeout(function timeout () {
 console.log('timeout');
},0);
setImmediate(function immediate () {
 console.log('immediate');
});
$ node timeout_vs_immediate.js
timeout
immediate
$ node timeout_vs_immediate.js
immediate
timeout
However, if you move the two calls within an I/O cycle, the immediate callback is always executed first: (Aşağıdaki örnekte callback fonksiyon scope'u içerisinde çağırdık bu fonksiyonları, dolayısıyla setImmediate kesinlikle önce çalışacaktır. )
// timeout_vs_immediate.js
var fs = require('fs')
fs.readFile(__filename, () => {
 setTimeout(() => {
   console.log('timeout')
 }, 0)
 setImmediate(() => {
   console.log('immediate')
 })
})
$ node timeout_vs_immediate.js
immediate
timeout
$ node timeout_vs_immediate.js
immediate
timeout

The main advantage to using setImmediate() over setTimeout() is setImmediate() will always be executed before any timers if scheduled within an I/O cycle, independently of how many timers are present.

process.nextTick()
nextTick method'unun aldığı fonksiyon, next tick'in queue'suna eklenir. nextTick method'u bitmeden event loop ilerleyemez.
process.nextTick() vs setImmediate()
  • process.nextTick() fires immediately on the same phase
  • setImmediate() fires on the following iteration or 'tick' of the event loop
In essence, the names should be swapped. process.nextTick() fires more immediately than setImmediate() but this is an artifact of the past which is unlikely to change. Making this switch would break a large percentage of the packages on npm. Every day more new modules are being added, which mean every day we wait, more potential breakages occur. While they are confusing, the names themselves won't change.
We recommend developers use setImmediate() in all cases because its easier to reason about (and it leads to code that's compatible with a wider variety of environments, like browser JS.) (process.nextTick ve setImmediate() fonksiyonlarının isimleri aslında birbirleriyle değiştirilmelidir. Çünkü process.nextTick() method'una argument olarak verilen fonksiyon current phase'in sonunda çalıştırılır. setImmediate() fonksiyonuna argument olarak verilen fonksiyon ise sonraki phase'de çalıştırılır(sonraki phase'in başında mı sonunda mı diye düşünme.) Yani aslında process.nextTick method'una argument olarka verilen fonksiyon, setImmediate() fonksiyonuna argument olarak verilen fonksiyondan daha önce çalışır. process.nextTick() yerine setImmediate() fonksiyonun kullanılması tavsiye edilmiştir. )

Why use process.nextTick()? HelloNode13
http://stackoverflow.com/questions/38140113/eventemitter-and-nexttick-in-nodejs/38140793#38140793
An example is running a function constructor that was to, say, inherit from EventEmitter and it wanted to call an event within the constructor:

const EventEmitter = require('events');
const util = require('util');
function MyEmitter() {
 EventEmitter.call(this);
 this.emit('event');
}
util.inherits(MyEmitter, EventEmitter);
const myEmitter = new MyEmitter();
myEmitter.on('event', function() {
 console.log('an event occurred!');
});
You can't emit an event from the constructor immediately because the script will not have processed to the point where the user assigns a callback to that event. So, within the constructor itself, you can use process.nextTick() to set a callback to emit the event after the constructor has finished, which provides the expected results: (Yukarıdaki örnekte MyEmitter isimli bir function constructor tanımladık. Bu constructor'dan bir object yarattığımızda event isimli bir event'i çağırıyoruz. Ancak böyle bir event henüz tanımlanmamıştır. Bu örnekte this.emit senkron olarak çağırılmıştır.
Bu örnekte ise this.emit asenkron olarak çağırılmıştır. process.nexttick'in aldığı fonksiyon sonraki tick'de çalışacaktır. Dolayısıyla myEmitter.on(...) daha önce çağırılacaktır, process.nexttick diyerek event queue'ya eklediğimiz fonksiyon ise sonraki tick'de çalışacaktır. bind ne işe yarıyor bilmiyorum,araştırılacak. )
const EventEmitter = require('events');
const util = require('util');
function MyEmitter() {
 EventEmitter.call(this);
 // use nextTick to emit the event once a handler is assigned
 process.nextTick(function () {
   this.emit('event');
 }.bind(this));
}
util.inherits(MyEmitter, EventEmitter);
const myEmitter = new MyEmitter();
myEmitter.on('event', function() {
 console.log('an event occurred!');
});
EventEmitter emits synchronously, which means that in your first example, the event being emitted (from the constructor) is emitted before a listener has been attached. Because events aren't queued or "saved", your event listener won't get the message (it simply started listening too late).
In your second example, the event is emitted from the constructor in the next cycle of the event loop (asynchronously). At that point, the code that adds the listener to myEmitter has already run, so at the time the event is being emitted the listener will receive it.
It's similar to this:
// synchronously: 'A' is logged before 'B'
console.log('A');
console.log('B');
// asynchronously: 'B' is logged before 'A'
process.nextTick(function() { console.log('A') });
console.log('B');
Explanation 3 :
process.nextTick put the callback on the next tick that is going to be executed, not at the end of the tick queue.
Node.js doc (http://nodejs.org/api/process.html#process_process_nexttick_callback) say: "It typically runs before any other I/O events fire, but there are some exceptions."
Explanation 4 :
Event Loop
The Node.js event loop runs under a single thread, this means the application code you write is evaluated on a single thread. Nodejs itself uses many threads underneath through libuv, but you never have to deal with with those when writing nodejs code.
Every call that involves I/O call requires you to register a callback. This call also returns immediately, this allows you to do multiple IO operations in parallel without using threads in your application code. As soon as an I/O operation is completed it's callback will be pushed on the event loop. It will be executed as soon as all the other callbacks that where pushed on the event loop before it are executed.
There are a few methods to do basic manipulation of how callbacks are added to the event loop. Usually you shouldn't need these, but every now and then they can be useful.
At no point will there ever be two true parallel paths of execution, so all operations are inherently thread safe. There usually will be several asynchronous concurrent paths of execution that are being managed by the event loop.
Limitations
Because of the event loop, node doesn't have to start a new thread for every incoming tcp connection. This allows node to service hundreds of thousands of requests concurrently , as long as you aren't calculating the first 1000 prime numbers for each request.
This also means it's important to not do CPU intensive operations, as these will keep a lock on the event loop and prevent other asynchronous paths of execution from continuing. It's also important to not use the sync variant of all the I/O methods, as these will keep a lock on the event loop as well.
If you want to do CPU heavy things you should ether delegate it to a different process that can execute the CPU bound operation more efficiently or you could write it as a node native add on.
Explanation 5 :
From Willem's answer:
The Node.js event loop runs under a single thread. Every I/O call requires you to register a callback. Every I/O call also returns immediately, this allows you to do multiple IO operations in parallel without using threads.
I would like to start explaining with this above quote, which is one of the common misunderstandings of node js framework that I am seeing everywhere.
Node.js does not magically handle all those asynchronous calls with just one thread and still keep that thread unblocked. It internally uses google's V8 engine and a library called libuv(written in c++) that enables it to delegate some potential asynchronous work to other worker threads (kind of like a pool of threads waiting there for any work to be delegated from the master node thread). Then later when those threads finish their execution they call their callbacks and that is how the event loop is aware of the fact that the execution of a worker thread is completed.
The main point and advantage of nodejs is that you will never need to care about those internal threads and they will stay away from your code!. All the nasty sync stuff that should normally happen in multi threaded environments will be abstracted out by nodejs framework and you can happily work on your single thread (main node thread) in a more programmer friendly environment (while benefiting from all the performance enhancements of multiple threads).
Below is a good post if anyone is interested: When is the thread pool used?
Explanation 6 :
process.nextTick puts a callback into a queue. Every callback in this queue will get executed at the very beginning of the next tick of the event loop.
It simply puts your function at the end of the event loop. 
Explanation 7:
Callbacks passed to process.nextTick will usually be called at the end of the current flow of execution, and are thus approximately as fast as calling a function synchronously.
For more info about event loop :
https://github.com/nodejs/node/blob/master/doc/topics/the-event-loop-timers-and-nexttick.md
https://www.youtube.com/watch?v=8aGhZQkoFbQ
http://blog.carbonfive.com/2013/10/27/the-javascript-event-loop-explained/
http://www.journaldev.com/7462/node-js-processing-model-single-threaded-model-with-event-loop-architecture  
http://blog.mixu.net/2011/02/01/understanding-the-node-js-event-loop/
http://stackoverflow.com/questions/17502948/nexttick-vs-setimmediate-visual-explanation
https://www.nczonline.net/blog/2013/07/09/the-case-for-setimmediate/
https://gist.github.com/brycebaril/ff86eeb90b53fd0c523e
https://www.reddit.com/r/node/comments/2la8zb/setimmediate_vs_processnexttick_vs_settimeout/
https://gist.github.com/a0viedo/0de050bb2249757c5def
http://www.tutorialspoint.com/nodejs/nodejs_event_loop.htm
https://nodesource.com/blog/understanding-the-nodejs-event-loop/

Hiç yorum yok:

Yorum Gönder