阿里云主机折上折
  • 微信号
Current Site:Index > Single-threaded and event loop

Single-threaded and event loop

Author:Chuan Chen 阅读数:47306人阅读 分类: Node.js

The Nature of Single-Threadedness

Node.js employs a single-threaded model for executing JavaScript code. This design means the main thread can only process one task at a time. Unlike multi-threaded environments, it eliminates the need to handle complex issues like thread synchronization and lock contention. The core advantage of single-threadedness lies in simplifying the concurrency model, but it also introduces potential performance bottlenecks.

function computeIntensiveTask() {
  let sum = 0;
  for (let i = 0; i < 1e9; i++) {
    sum += i;
  }
  return sum;
}

console.log('Start');
computeIntensiveTask(); // Blocks the main thread
console.log('End'); // Will execute after a long delay

The Event Loop Mechanism

The event loop is key to Node.js's non-blocking I/O implementation. It is essentially an infinite loop that continuously checks the event queue and executes callbacks. The entire mechanism is divided into multiple phases, each handling specific types of tasks:

  1. Timers Phase: Executes setTimeout and setInterval callbacks
  2. I/O Callbacks Phase: Processes network, file, and other I/O events
  3. Idle/Prepare Phase: For internal use
  4. Poll Phase: Retrieves new I/O events
  5. Check Phase: Executes setImmediate callbacks
  6. Close Callbacks Phase: Handles events like socket.on('close')
setTimeout(() => console.log('Timeout'), 0);
setImmediate(() => console.log('Immediate'));

// Output order may vary depending on event loop startup time

Non-Blocking I/O Implementation

Node.js achieves true asynchronous I/O through the libuv library. When encountering I/O operations, the main thread delegates the task to the system kernel and continues executing subsequent code. After the kernel completes the operation, it places the result in the event queue for the event loop to process.

const fs = require('fs');

console.log('Start reading file');
fs.readFile('largefile.txt', (err, data) => {
  console.log('File reading completed');
});
console.log('Continue with other tasks');

// Output order:
// Start reading file
// Continue with other tasks
// File reading completed

Microtasks and Macrotasks

Tasks in the event loop are categorized by priority:

  • Microtasks: process.nextTick, Promise callbacks
  • Macrotasks: setTimeout, setInterval, I/O operations

Microtasks execute immediately after the current phase ends, taking precedence over macrotasks.

Promise.resolve().then(() => console.log('Promise'));
process.nextTick(() => console.log('nextTick'));
setTimeout(() => console.log('Timeout'), 0);

// Output order:
// nextTick
// Promise
// Timeout

Common Performance Pitfalls

CPU-intensive tasks can block the event loop in a single-threaded model:

// Bad practice: Synchronously encrypting large files
const crypto = require('crypto');
function encryptLargeFile() {
  const data = Buffer.alloc(1e8); // 100MB data
  return crypto.createHash('sha256').update(data).digest('hex');
}

// Correct approach: Use streaming or worker threads
const { Worker } = require('worker_threads');
function asyncEncrypt(filePath) {
  return new Promise((resolve) => {
    const worker = new Worker('./encrypt-worker.js', { workerData: filePath });
    worker.on('message', resolve);
  });
}

Event Loop Monitoring

Node.js provides performance monitoring interfaces to detect event loop latency:

const { monitorEventLoopDelay } = require('perf_hooks');

const histogram = monitorEventLoopDelay();
histogram.enable();

setInterval(() => {
  console.log(`Event loop delay(ms): 
    p50=${histogram.percentile(50)/1e6}, 
    p99=${histogram.percentile(99)/1e6}`);
  histogram.reset();
}, 1000);

Practical Optimization

Best practices for web servers:

const express = require('express');
const app = express();

// Error-handling middleware should be declared first
app.use((err, req, res, next) => {
  console.error(err);
  res.status(500).send('Server Error');
});

// CPU-intensive routes should use worker threads
app.get('/compute', async (req, res) => {
  const result = await runInWorker('./compute.js');
  res.json({ result });
});

// I/O-intensive routes can be handled directly
app.get('/data', (req, res) => {
  db.query('SELECT * FROM large_table', (err, data) => {
    if (err) return next(err);
    res.json(data);
  });
});

Advanced Pattern Applications

Leveraging event loop characteristics for flow control:

class RateLimiter {
  constructor(limit) {
    this.queue = [];
    this.active = 0;
    this.limit = limit;
  }

  async execute(task) {
    if (this.active >= this.limit) {
      await new Promise(resolve => this.queue.push(resolve));
    }

    this.active++;
    try {
      return await task();
    } finally {
      this.active--;
      if (this.queue.length) {
        this.queue.shift()();
      }
    }
  }
}

// Usage example
const limiter = new RateLimiter(5);
for (let i = 0; i < 100; i++) {
  limiter.execute(() => fetch('https://api.example.com/data/' + i));
}

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

上一篇:非阻塞I/O模型

下一篇:CommonJS模块系统

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.