跳到主要内容

3 篇博文 含有标签「nodejs」

查看所有标签

进程、线程与nodejs

· 阅读需 9 分钟

这篇文章是补齐我一些关于进程和线程的基础知识,以及他们和 nodejs 的关系。形式为 chatgpt 问答。

Q: 什么是计算机的进程和线程

在计算机科学中,进程(Process)和线程(Thread)是操作系统中用于执行程序的两个重要概念。

进程是指在计算机中正在运行的程序的实例。每个进程都有自己独立的内存空间、程序代码和数据。进程之间是相互独立的,它们不能直接访问其他进程的内存空间。进程有自己的程序计数器(Program Counter)、寄存器集合、打开的文件和其他系统资源等。操作系统通过调度算法来分配处理器时间给不同的进程,使它们能够并发执行。

线程是进程内的执行单元。一个进程可以包含多个线程,这些线程共享进程的内存空间和系统资源。线程之间可以更方便地进行通信和共享数据,因为它们可以直接读取和修改进程的内存。线程拥有自己的程序计数器、栈、寄存器集合,但它们共享进程的其他资源。由于线程共享了大部分资源,所以线程间的切换开销较小,可以实现更高效的并发执行。

与进程相比,线程具有以下优点:

  1. 线程的创建和销毁开销较小,速度更快。
  2. 线程之间的切换开销较小,响应时间更短。
  3. 线程共享进程的内存空间,可以更方便地进行数据共享和通信。

然而,线程也存在一些问题:

  1. 线程之间共享内存,需要进行同步和互斥操作,以避免数据竞争和不一致性。
  2. 一个线程的错误可能会影响到整个进程的稳定性。
  3. 多线程编程更加复杂,需要考虑线程同步、死锁等并发编程问题。

总结起来,进程和线程是操作系统中用于执行程序的两个基本概念。进程是程序的实例,具有独立的内存空间和系统资源;线程是进程内的执行单元,共享进程的内存空间。线程可以实现更高效的并发执行,但也需要注意并发编程带来的问题。

Q: 以 nodejs 处理网络请求为例,说明进程和线程的关系

在 Node.js 中,网络请求是通过事件驱动的非阻塞 I/O(Input/Output)模型来处理的。这个模型基于单线程的事件循环机制。

在 Node.js 中,一个进程通常指的是一个 Node.js 实例,它运行在操作系统的一个进程中。这个进程是由 Node.js 程序启动的,并拥有自己的内存空间、程序代码和系统资源。

在这个进程内部,Node.js 使用了单线程的事件循环机制来处理网络请求。事件循环是一个无限循环,不断地监听事件队列中的事件,并执行相应的回调函数。当有网络请求到达时,Node.js 将其包装成一个事件,并将其放入事件队列中,然后继续执行下一个事件。这样,Node.js 可以在处理一个请求的同时监听其他请求。

这里的关键点是,尽管 Node.js 使用单线程来处理事件循环,但它通过非阻塞 I/O 操作的方式,实现了对并发请求的高效处理。当一个请求需要进行网络 I/O 操作(如读取文件、发送 HTTP 请求等)时,Node.js 会将这个操作交给底层操作系统,并立即返回控制权。在等待 I/O 操作完成的期间,Node.js 可以继续处理其他请求,而不会被阻塞。

底层操作系统通常会使用多线程来处理具体的 I/O 操作,例如在读取文件时,可能会有一个线程负责从磁盘中读取数据,并将数据返回给 Node.js。这个过程对于 Node.js 来说是透明的,它只需要等待 I/O 操作完成,并通过事件机制得到通知。

因此,在 Node.js 中,进程代表了一个 Node.js 实例,而线程的管理是由底层操作系统负责的。Node.js 利用单线程的事件循环机制和底层操作系统的多线程 I/O 处理,实现了高效的网络请求处理。

Q: nodejs 进程只能利用到一个核心吗

在默认情况下,Node.js 是单线程的,它使用单个线程来执行 JavaScript 代码。这意味着一个 Node.js 进程只能利用到一个 CPU 核心。

然而,Node.js 通过使用事件驱动、非阻塞 I/O 和异步编程模型来实现高效的并发处理。它使用事件循环机制来处理并发请求,而不是为每个请求创建一个新的线程。这种设计使得单个 Node.js 进程可以处理大量的并发请求,而无需为每个请求分配一个单独的线程。

尽管 Node.js 的 JavaScript 代码执行是单线程的,但它可以利用多核处理器的优势。通过使用集群模块或者进程管理工具,可以创建多个 Node.js 进程并将请求分发给它们,从而实现并行处理。每个 Node.js 进程都可以运行在不同的 CPU 核心上,以充分利用多核处理器的计算能力。

例如,可以使用 Node.js 的 cluster 模块来创建一个主进程和多个工作进程,每个工作进程都可以运行在不同的 CPU 核心上,以并行处理请求。主进程负责接收和分发请求,而工作进程负责实际的请求处理。

另外,还可以使用一些第三方模块和工具,如 PM2、Nginx 等,来管理和扩展 Node.js 进程,以实现负载均衡和高可用性。

需要注意的是,多进程的部署需要考虑进程间的通信、数据同步和共享资源等问题。此外,由于每个进程都会占用一定的系统资源,过多的进程数量可能会导致系统负载增加。因此,在实际部署时,需要根据具体的场景和硬件资源配置来合理设置进程数量。

javascript 模块机制

· 阅读需 5 分钟

前言

最近阅读《深入浅出 nodejs》和《nodejs 实战》的时候,都把模块系统放在前面的位置介绍,可见其重要性。模块机制帮助开发者将代码分割成独立的、功能明确的块,可以单独开发、测试和维护。因为之前写的文章逻辑比较混乱,故重新写一下。

JavaScript 主要有以下几种模块机制:

  • ES6 模块 (ES Modules)
  • CommonJS 模块
  • AMD 模块
  • UMD 模块

其中,最常见的莫过于前两种。

ES Modules

这是在 ES6(ECMAScript 2015)中引入的标准模块系统。ES6 模块通过 import 和 export 关键字来导入和导出模块。ES6 模块具有以下特点:

  • 静态结构:模块的依赖关系在编译时就能确定,不需要在运行时解析。这使得工具可以进行静态分析和优化。(这是不是基于 Nodejs 的后端代码也要采用 ES6 模块机制的原因?)
  • 文件即模块:每个文件被视为一个独立的模块。
  • 默认导出和命名导出:可以导出多个命名导出,也可以有一个默认导出。从格式来看,采用命名导出时,导入文件中,模块名外带花括号,而默认导出则相反。
  • 多入口加载,结合第一点,因而可以实现按需加载。

示例:

导出模块 (math.js)

// Named exports
export function add(a, b) {
return a + b;
}

export const PI = 3.14159;

// Default export
export default function subtract(a, b) {
return a - b;
}

导入模块 (main.js)

// Importing named exports
import { add, PI } from "./math.js";

console.log(add(2, 3)); // 5
console.log(PI); // 3.14159

// Importing the default export
import subtract from "./math.js";

console.log(subtract(5, 2)); // 3

注意事项:

  • 模块路径:相对路径或绝对路径需要明确指定。
  • 顶级作用域:ES6 模块在模块顶级作用域中运行,因此每个模块都有自己的独立作用域。

CommonJS

CommonJS 模块是 Node.js 使用的模块系统,通过 require 导入模块和 module.exports 导出模块。CommonJS 模块具有以下特点:

  • 动态加载:模块在运行时加载,require 是一个同步操作。
  • 整个模块导出:可以导出一个对象,该对象包含多个属性和方法。
  • 单一出口:模块通过 module.exports 导出单一对象。

示例:

导出模块 (math.js)

function add(a, b) {
return a + b;
}

const PI = 3.14159;

function subtract(a, b) {
return a - b;
}

module.exports = {
add,
PI,
subtract,
};

导入模块 (main.js)

const math = require("./math");

console.log(math.add(2, 3)); // 5
console.log(math.PI); // 3.14159
console.log(math.subtract(5, 2)); // 3

注意事项:

  • 动态加载:模块在代码执行到 require 语句时才会加载。
  • 缓存:一旦模块加载,它会被缓存,再次 require 相同模块时将返回缓存的版本。

比较 ES6 模块和 CommonJS 模块

特性ES6 模块CommonJS 模块
语法import / exportrequire / module.exports
加载方式静态加载动态加载
依赖关系编译时确定运行时确定
顶级作用域模块级作用域文件级作用域
是否支持浏览器否(需要打包工具,如 Browserify)
缓存机制浏览器端和服务器端缓存均支持服务器端缓存
默认导出支持需要手动指定

nodejs构建服务器

· 阅读需 2 分钟

从nodejs服务器想到的

服务器的定义是,监听请求,处理请求和做出响应。除了nodejs外,其他语言也有对应的web server框架,比如java的tomcat。

用下列代码实现一个简单的服务器:

var http = require('http')
var fs = require('fs')
http.createServer(function (request, response) {
fs.readFile('1.txt', (err, data)=>{
response.setHeader("Access-Control-Allow-Origin", "*")
response.end(data)
})
}).listen(3000, function () {
console.log('running')
})

用node命令执行上述代码后,就可以发送请求了。我们总说请求,可是请求究竟有哪些类型呢?网上将http请求分为get\post等8种类型,我觉得,从另一个角度,可以分为全局型和局部型。全局型是浏览器输入框改变,则会发起请求,并刷新页面。而局部型如ajax,只更新dom树几个节点的内容,不刷新页面。

全局型请求,是通过更改末尾地址实现。而上面的代码对于所有请求的响应都相同。所以,可以通过路由分发来实现不同地址响应。