发布于 2026-01-05 8 阅读
0

多线程:事件循环与线程池等等……

多线程:事件循环与线程池等等……

😩 每当我们想到运行程序时,我们总是专注于运行代码,但我们并不关心是谁在运行代码。

你知道是谁运行代码,是谁将程序送入内存以及程序是如何执行的吗?

多线程梗

😎 所以,有一种叫做线程的东西。它负责使用主内存(RAM)、CPU和存储空间,或者更广泛地说,使用我们机器的资源来运行你的程序/代码。

线程只不过是一个拥有线程块、堆栈和堆访问权限的工作进程。

但是栈、堆和线程块到底是什么?

📌 栈、堆、线程块

Stack Overflow 梗

  • 每当你编写一个函数时
def add(a, b):
   return a+b
Enter fullscreen mode Exit fullscreen mode

然后,当我们运行这段代码时,我们会在主内存(RAM)中声明该函数,并将指向该函数的指针存储在堆栈中。

栈与堆
图片参考:-链接

✨ 因此,栈是作用域级别的内存空间,用于存储像 a 和 b 这样的局部变量;而堆是动态内存,用于存储指针、动态数组和对象(类的实例)。

📌 当函数执行完毕时,栈会被清空,就像被刷新一样;但堆在某些编程语言中需要手动清理,而在另一些编程语言中则有垃圾回收机制。

表情包

别担心,跟着做你肯定能学会,请点赞并保存这篇文章以支持我们。

😎 现在,我们了解了栈和堆,它们用于在程序运行时存储动态数据。

但是我们这位能扛起整件东西的家伙呢?(帖子内容正确)

所以,线程有线程块。

线程块

因此,线程块包含一些字段和一个存储单元,其中包含运行程序所需的信息,并用于访问共享空间或自身空间。共享空间指的是堆内存空间,而自身空间指的是栈内存空间。


💀接下来,我们将介绍什么是事件循环和线程池。

如果有人使用 JavaScript 工作,他/她可能知道 JavaScript 是一种单线程语言,这意味着无论你的代码是什么,它都将由单个工作线程执行。

但是,如果有人使用 Java、C、C++ 等编程语言,我们可以并行使用多个线程。

梗图:那是什么?

各位耐心点,你们会明白的💯

📌 多线程

假设你有两个函数(用JAVA编写)

class Main{

public void static main(String[] args){
     System.out.println("Hello world");
}

public static void add(int a, int b){
     System.out.println(a+b);
}

public static void substract(int a, int b){
     System.out.println(a-b);
}

}

Enter fullscreen mode Exit fullscreen mode

😎 现在,线程 1 可以执行 add() 函数并开始执行,线程 2 可以执行 substract() 函数并开始执行,对吗?因为它们是相互独立的。

所以,这里存在一种叫做上下文切换的机制。比如说,如果线程 1 正在等待用户输入,那么线程 2 就会运行,上下文就会从线程 1 切换到线程 2。

😎 线程 1 的当前状态将被存储在其代码块中,线程 2 将继续运行。多线程的主要目的是确保 CPU 不会进入空闲状态,而是持续处于使用状态。

😩 但是在 Javascript 中我们只有一个线程,如何才能实现并行化呢?

🫶 这里是事件循环。

📌 事件循环 vs 线程池

假设JS代码如下:


function add(){
    setTimeout(()->{
         console.log("I am lazy");
    }, 1000);
    console.log("I am active");
}

Enter fullscreen mode Exit fullscreen mode

这里我们可以看到有一个代码块正在等待 1000 毫秒(1 秒),之后才会执行。但还有其他独立的代码行可以同时运行。所以,

setTimeout(()->{
         console.log("I am lazy");
    }, 1000);
Enter fullscreen mode Exit fullscreen mode

可以进入等待阶段,与此同时

console.log("I am active");
Enter fullscreen mode Exit fullscreen mode

可以运行。因此,setTimeout() 函数会放在事件循环中,等待同步代码运行完毕。而这段等待的代码是异步代码。

事件循环
图片参考:-链接

❤️ 在这里,事件循环会不断循环,并检查回调队列中是否有需要运行的程序(异步代码会进入这个队列)。所有同步代码执行完毕后(当同步代码栈为空时),异步代码才会运行。

😩 这算是一种阻塞机制吧?如果同步代码陷入无限循环,所有异步代码都会停止运行,对吧?没错,这就是问题所在,所以超时机制需要仔细配置。

现在,我们来看一下线程池:

线程池

🥹 现在的问题是,当我们读取一个大文件并将其设为异步操作时,如果异步代码将来也要运行,那么其他同步代码是如何运行的?

这里就是线程池的用武之地。例如,要读取一个文件,你会这样做。

let filePath = "test.txt";
let fileContent = fs.readFile(filePath, "utf-8", (data, err)->{
    return data;
})

Enter fullscreen mode Exit fullscreen mode

现在,我们的操作系统有线程,JavaScript 会将读取文件的任务分配给操作系统线程池中的一个线程,而我们的操作系统有一些系统调用,它会使用这些调用来读取文件并将其保存到缓冲区中。

此缓冲区仅存在于内存中。

byte[] buff;
Enter fullscreen mode Exit fullscreen mode

而且还有一个回调函数

(data, err)->{
    return data;
}
Enter fullscreen mode Exit fullscreen mode

文件读取完成后,该函数将被调用。然后,JavaScript 主线程会将 buff[] 中的内容复制到 data 数组中。


所以,在JS中,一切看起来都是并行的,但实际上并非如此。

🔥 今天就到这里。我们学习了……

  • 线程、栈、堆
  • 线程块
  • 多线程,上下文切换
  • 事件循环,线程池

关注我们,获取更多深度文章🫶

访问我的 YouTube 频道了解更多信息:链接[语言:印地语,印度🇮🇳]

文章来源:https://dev.to/singhdevhub/multithreading-event-loops-vs-thread-pools-and-more-48di