进程
进程是计算机运行程序的实例,它是操作系统对一个正在运行中的程序的抽象。每个进程都有自己的地址空间,包括代码、数据、堆栈等,以及运行时所需的资源,如打开的文件、设备等。进程是操作系统进行资源分配和调度的基本单位,它可以独立运行、中断和恢复。
一个应用程序,总是通过操作系统启动的,当操作系统启动一个应用程序时,会给其分配一个进程
一个进程拥有独立的、可伸缩的内存空间,原则上不受其他进程干扰。
进程之间是可以通信的,只要两个进程双方遵守一定的协议,比如ipc
CPU在不同的进程之间切换执行
虽然一个应用程序在启动时只有一个进程,但它在运行的过程中,可以开启新的进程,进程之间仍然保持相对独立
如果一个进程是直接由操作系统开启,则它叫做主进程
如果一个进程B是由进程A开启,则A是B的父进程,B是A的子进程,子进程会继承父进程的一些信息,但仍然保持相对独立
// nodejs 中开启子进程
const childProcess = require("child_process"); // 导入内置模块
childProcess.exec(在子进程运行的命令, (err, out, stdErr) => {
// 回调函数中可以获取子进程的标准输出,这种数据交互是通过IPC完成的,nodejs已经帮你完成了处理
// err:开启进程过程中发生的错误
// out: 子进程输出的正常内容
// stdErr: 子进程输出的错误内容
// 子进程发生任何的错误,绝不会影响到父进程,它们的内存空间是完全隔离的
});
// 过去,nodejs没有提供给用户创建线程的接口,只能使用进程的方式
// 过去,nodejs还提供了cluster模块,通过另一种模式来创建进程
// 现在,nodejs已经提供了线程模块,对进程的操作已经很少使用了
线程
线程是计算机科学中的基本概念,是进程中的执行单元。一个进程可以包含多个线程,每个线程可以独立执行不同的任务。线程是操作系统调度的最小单元,它拥有自己的程序计数器、堆栈和一组寄存器。不同的线程之间可以共享同一进程的资源,如内存空间和文件句柄。线程可以同时执行多个任务,提高程序的并发性和响应能力。常见的线程模型有用户级线程和内核级线程。
操作系统启动一个进程(无论是主进程,还是子进程),都会自动为它分配一个线程,称之为主线程
程序一定在线程上运行!!
主线程在运行的过程中,可以创建多个线程,这些线程称之为子线程
当操作系统命令CPU去执行一个进程时,实际上,是在该进程的多个线程中切换执行
线程和进程很相似,它们都是独立运行,最大的区别在于:线程的内存空间没有隔离,共享进程的内存空间,线程之间的数据不用遵守任何协议,可以随意使用
什么时候要使用线程?
使用线程的主要目的,是为了充分使用多核cpu。线程执行过程中,尽量的不要阻塞。
最理想的线程效果:
- 线程数等于cpu的核数
- 线程永不阻塞
- 没有io
- 只存在大量运算
- 线程相对独立,几乎不使用共享数据
线程一般处理cpu密集型操作(运算操作),而io密集型操作不适合使用线程,而适合使用异步
为了避免线程执行过程中共享数据产生的麻烦,nodejs使用独特的线程机制来尽力规避:
// 创建子线程的父线程
const { Worker } = require("worker_threads");
const worker = new Worker(线程执行的入口文件, {
workerData: 开启线程时向其传递的数据,
}); // worker是子线程实例
// 通过EventEmitter监听子线程的事件
worker.on("exit", () => {
// 当子线程退出时运行的事件
});
worker.on("message", (msg) => {
// 收到子线程发送的消息时运行的事件
});
worker.postMessage(任意消息); // 父线程向子线程发送任意消息
worker.terminate(); // 退出子线程
const {
isMainThread, // 是否是主线程
parentPort, // 用于与父线程通信的端口
workerData, // 获取线程启动时传递的数据
threadId, // 获取线程的唯一编号
} = require("worker_threads");
parentPort.on("message", (msg) => {
// 当收到父线程发送的消息时,触发的事件
});
parentPort.postMessage(workerData); // 向父线程发送消息
Nodejs 线程机制
Node.js 实际上是基于单线程的事件驱动模型进行开发的,这是因为 JavaScript 语言本身是单线程的。但是,Node.js 通过使用事件循环和非阻塞 I/O 模型实现了异步操作,从而能够处理大量的并发请求。
Node.js的线程机制包括以下几个部分:
-
事件循环(Event Loop):Node.js中的事件循环采用了Libuv库来实现,它负责管理和调度事件以及回调函数的执行。事件循环的核心思想是将事件和回调函数放入事件队列中,然后在适当的时候调用回调函数来处理事件。
-
单线程:Node.js中只使用了一个线程来处理所有的请求。这个线程会不断地处理事件和回调函数,而不阻塞其他请求的处理。这样可以提高系统的吞吐量和并发性能。
-
非阻塞I/O:Node.js采用非阻塞的I/O模型,也就是说在进行I/O操作时不会阻塞其他的操作。当有一个I/O操作需要进行时,Node.js会将该操作交给操作系统处理,并立即返回一个回调函数,然后继续处理其他请求。当操作系统完成I/O操作后,会将结果返回给Node.js,并通过事件队列中的回调函数来处理。
-
异步编程:Node.js鼓励使用异步编程的方式来处理请求,以便提高系统的性能和响应速度。通过使用回调函数、Promise、Async/Await等方式,开发者可以在不阻塞主线程的情况下处理大量的并发请求。
Node.js 的核心机制是事件循环,它是实现异步操作的基础。事件循环是一个不断循环的过程,它会检查事件队列中是否有待处理的事件,如果有则执行相应的回调函数。这种机制可以实现非阻塞的 I/O 操作,当有新的 I/O 请求时,不需要等待结果返回,而是直接进行下一个事件的处理。
总结来说,Node.js使用单线程的事件循环机制和非阻塞I/O模型来实现高性能的异步编程。这种机制能够更好地利用系统资源,提高系统的吞吐量和并发性能。