Multi-tasks With Process & Thread

现代操作系统都是「多任务」的,也就是操作系统可以「并发」处理多个任务,比如可以在浏览页面的时候同时播放音乐,但是一般来说我们的 PC 一般都只有一个物理 CPU,那么是如何做到只有一个 CPU 的情况下并发处理多个任务的呢,我们简单探究一下。

前置知识

我们先简单熟悉一下需要理解的 CPU 硬件相关的术语

1.Sockets(physical CPU): 物理 CPU,指我们主板上实际插入的 CPU,一般来说 PC 只有一个,服务器可能会有多个
2.Cores: CPU 物理核心,CPU 商品上宣传的一共几核指代的就是这个
3.Logical Processors: 逻辑处理器,如果采用超线程(多线程)技术的话,会比物理核心数多

总的来说: Logical Processors = Sockets _ Cores _ SMT(HT) Multiple
逻辑处理器数量也就代表了操作系统认为能「并行」执行的任务的最高数量

并发 VS 并行

我们对「并发」和「并行」先下个定义,「并发」指的是系统允许多个任务同时存在,「并行」则指的是系统支持多个任务同时执行,「并发」和「并行」的关键区别在于是否能同时执行。在只有单一逻辑处理器的情况下,我们的操作系统只能「并发」执行任务,比如早期的单核 CPU 电脑,但是我们仍然可以边听歌边浏览网页,这是因为 CPU 速度足够快,可以在系统的使用过程中快速切换任务,这样我们就感觉到多个任务同时存在在单一逻辑处理器的情况下,虽然我们可以「并发」执行任务,实际上我们同时也只能执行一个任务,对于 IO 密集类型的任务,我们用到 CPU 的时间不多,决定任务快慢的往往是硬盘以及网络等硬件,「并发」执行也未尝不可,但是对于计算密集型的任务,我们需要占用更多的 CPU 时间,如果「并发」执行,则往往会造成任务的卡顿(响应时间过长),因此我们需要「并行」的执行该任务,而逻辑处理器的数量代表了能「并行」执行任务的最高数量,这也是为什么现在的处理器大多是多核处理器的原因所在。

进程 VS 线程

我们使用的一个个程序可以称为「进程」(process),而 process 下可以开辟多个「线程」(thread),这里引用一下 Microsoft 官方对于进程和线程的解释About Processes and Threads:

Each process provides the resources needed to execute a program. A process has a virtual address space, executable code, open handles to system objects, a security context, a unique process identifier, environment variables, a priority class, minimum and maximum working set sizes, and at least one thread of execution. Each process is started with a single thread, often called the primary thread, but can create additional threads from any of its threads.

A thread is the entity within a process that can be scheduled for execution. All threads of a process share its virtual address space and system resources. In addition, each thread maintains exception handlers, a scheduling priority, thread local storage, a unique thread identifier, and a set of structures the system will use to save the thread context until it is scheduled. The thread context includes the thread’s set of machine registers, the kernel stack, a thread environment block, and a user stack in the address space of the thread’s process. Threads can also have their own security context, which can be used for impersonating clients.

在操作系统层面,process 相互独立,拥有一块独立的虚拟地址空间(内存中),而同一 process 下的 thread 共享该虚拟地址空间,这也是 process 和 thread 最典型,最根本的区别

多进程 VS 多线程

假如我们现在要开发一款浏览器,浏览器的基础功能包括 HTTP 请求,GUI 渲染等功能,如果我们采用单线程来开发,那么势必会遇到一个问题: 当需要网络请求的时候,我们的浏览器就会卡住,所有的用户操作如输入等都没有响应,等网络请求完成,我们才可以进行后续操作,非常影响用户体验,这也是为什么像浏览器这样的程序大多都是多线程的原因,我们需要任务同时进行。但是我们前面讲到的多进程也可以多任务同时进行,那么问题就来了,当我们需要实现多任务的时候,多进程和多线程该如何选择呢?

Read more

CommonJS modules in Node.js

Introduction

最初的 JavaScript 设计时没有模块化相关的概念,但是随着前端的发展及其工程化的引进和借鉴,相关的模块化思想也被引入了前端领域,其中CommonJS的模块化解决方案一直屹立不倒。为了更好的理解CommonJS,本篇文章依据Module:CommonJS modules相关文档,借鉴loader.js的相关源码,自己手动实现一个CommonJS module;

The Module Wrapper

每个被 require 的模块,在执行之前都被一个函数包裹,类似于:

1
2
3
(function (exports, require, module, __filename, __dirname) {
//Module code actually lives in here
});

这样做可以防止模块内的定义的变量提升到全局作用域,同时提供了moduleexports等变量供当前模块使用

What require() does:

require()加载模块的顺序具体可以参考这段伪代码,简单概括如下:

  1. 优先加载诸如path,fs等核心模块
  2. 先从缓存中找对应的模块是否已加载,没有则进行下一步
  3. 加载对应的以/, ./, ../开头的文件,如果不带文件后缀,则按照.js, .json,.node依次尝试,有的话加载对应的文件内容
  4. 加载对应的以/, ./, ../开头的文件夹,先查找文件夹下的package.json,如果package.json定义了”main”,则加载”main”对应的内容,没有则寻找对应的文件夹下的 index 文件,然后按照第三步的加载文件顺序依次查找并加载
  5. 加载对应的node_modules文件夹下的内容
  6. 抛出”not found”错误

对于最新版本的Node.js则增加了ECMAScript modules相关的加载规则,有兴趣的可以了解一下。

Read more