11

Let's take the simple code snippet:

var express = require('express');
var app = express();
var counter = 0;

app.get('/', function (req, res) {
   // LOCK
   counter++;
   // UNLOCK
   res.send('hello world')
})

Let's say that app.get(...) is called a huge number of times, and as you can understand I don't want the line counter++ to be executed concurrently by the two different threads.

Therefore, I want to lock this line that only one thread can have access to this line. My question is how to do it in node.js?

I know there is a lock package: https://www.npmjs.com/package/locks, but I'm wondering whether there is a "native" way of doing it without an external library.

Derek Pollard
  • 6,953
  • 6
  • 39
  • 59
CrazySynthax
  • 13,662
  • 34
  • 99
  • 183
  • 4
    Node is still single threaded. Don’t mix up parallel and concurrent. https://bytearcher.com/articles/parallel-vs-concurrent/ – jens Jun 22 '18 at 05:45
  • Why do you want to block thread? If you block thread none of the requests will execute all will be in the queue. It's not good practice to block the thread in Node.js – Rahul Sharma Jun 22 '18 at 06:05

3 Answers3

16

I don't want the line counter++ to be executed concurrently by the two different threads

That cannot happen in node.js with just regular Javascript coding.

node.js is single threaded and event-driven, so there's only ever one piece of Javascript code running at a time that can access that variable. You do not have to worry about the typical pre-emptive concurrency issues of multi-threaded systems.

That said, you can still have concurrency issues in node.js if you are using asynchronous code because the node.js asynchronous model returns control back to the system to process the next event and the asynchronous callback gets called on some future event. But, the concurrency issues are non-pre-emptive so you fully control when they can occur.

If you show us your actual code in your app.get() route handler, then we can advise more specifically about whether you do or don't have a concurrency issue there or not. And, if you do, we can advise on how to best deal with that.

Threads in the thread pool are all native code that runs behind the scenes. They only trigger actual Javascript to run by queuing events through the event queue. So, because all Javascript that runs is serialized through the event queue, you only get one piece of Javascript ever running at a time. The basic scheme of the event queue is that the interpreter runs a piece of Javascript until it returns control back to the system. At that point, the interpreter looks in the event queue and if there's an event waiting, it pulls that event out and calls the callback associated with that event. Meanwhile, if there is native code running in the background, when it completes, it adds an event to the event queue. That event is not processed until the current Javascript returns control back to the system and it can then grab the next event out of the event queue. So, it's this event-queue that serializes running only one piece of Javascript at a time.

Edit: Nodejs does now have WorkerThreads which enable separate threads of Javascript, but each thread has its own heap and its own variables so a variable from one thread cannot be directly accessed from another thread. You can configure shared memory that both WorkerThreads can access, but that isn't straight variables, but blocks of memory and if you want to use shared memory, then you do indeed need to code your own synchronization methods to make sure you are atomically accessing the variable. The code you show in your question is not using any of this so the access to the counter variable is already atomic and cannot be simultaneously accessed by any other Javascript, even if you are using WorkerThreads.

jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • OK. So you're saying that even if I have 100 threads in thread pool, I'll never see two threads that execute in parallel ? – CrazySynthax Jun 22 '18 at 05:58
  • @CrazySynthax - Javascript itself doesn't have threads. So, I don't know what you mean by 100 threads in a thread pool. If you're talking about the thread pool used internally in node.js. that is for native code only. When a native code action (like say a file sytem operation) wants to notify completion back to Javascript, it goes through the event queue as described in my answer. There are never two pieces of Javascript running at the same time. node.js does not have Javascript threads. All native code threads are serialized through the event queue for calling Javascript callbacks. – jfriend00 Jun 22 '18 at 06:00
  • @CrazySynthax - Added several edits to my answer to fill in some more details. – jfriend00 Jun 22 '18 at 06:10
  • can we conclude that once a callback started to execute, CPU will never leave it before it fully ends? – CrazySynthax Jun 22 '18 at 09:31
  • 1
    @CrazySynthax - Yes, that is true. It will execute single threaded until it returns control back to the interpreter at which point the interpreter will pull the next event from the event queue and call the callback associated with it. Note, due to the asynchronous design of Javascript, the first callback may not actually be done with its work. It may have returned and scheduled some other event to occur in the future (timer, I/O request, etc...) that will trigger a new callback in the future where it will finish its work. But, it will run uninterrupted until it returns. – jfriend00 Jun 22 '18 at 15:59
  • 1
    I wonder how would you deal in the situation when counter is saved in DB and you have multi instance server? – Vitaliy Markitanov Dec 01 '20 at 21:04
  • 1
    @VitaliyMarkitanov - For multi-process access to a database, you have to use atomic database operations. For example, you don't fetch a counter, increment it and write it back because that's subject to multi-process race conditions. Instead, you use the atomic operations your database provides. That might be a lock or that might be an atomic increment operation or something else. Each database has its own set of concurrency/atomic features expressly for these types of problems. This is part of proper database coding. – jfriend00 Dec 01 '20 at 22:41
2

If you block thread none of the requests will execute all will be in the queue.

It 's not good practice to block the thread in Node.js

var express = require('express');
var app = express();
var counter = 0;

const getPromise = () => {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve('Done')
        }, 100);
    });
}

app.get('/', async (req, res) => {
    const localCounter = counter++; 
    // Use local counter for rest of operation so value won't vary

    // LOCK: Use promise/callback 
    await getPromise(); // Not locked but waiting for getPromise to finish

    console.log(localCounter); // Same value before lock

    res.send('hello world')
})
Rahul Sharma
  • 9,534
  • 1
  • 15
  • 37
1

Node.js is single-threaded, which means that any single process running your app will not have data races like you anticipate. In fact, a quick inspection of the locks library shows that they use a boolean flag and a system of Array objects to determine whether something is locked or not.

You should only really worry about this if you plan on sharing data with multiple processes. In that case, you could use Alan's lockfile approach from this stackoverflow thread here.

Andres Salgado
  • 243
  • 2
  • 7
  • So can we conclude that once a thread (in thread pool) starts to execute, it will never be interrupted until it terminates? – CrazySynthax Jun 22 '18 at 09:28
  • 1
    There is only ever 1 thread in the thread pool in Node.js. However, jfriend00 does a good job explaining how Node accomplishes concurrency despite being single-threaded. Put simply, it is safe to assume that as long as a statement is executing, it will not be manipulated in such a way that you have to worry about data races (reads and writes happening at the same time). However, it is completely possible for callbacks to be interleaved in such a way that while one callback is waiting for something to complete, another can be started. Again, jfriend00 explains this with the event queue. – Andres Salgado Jun 22 '18 at 13:54