理解JS的单线程、异步

单线程与多线程

  • 首先多线程能做什么

例如 Python 就是一门能多线程编程的语言。现在我们用 Python 来试一下多线程(可以只看结果)。

import threading

# 线程需要跑的函数
def run_thread(thread_name):
    for i in range(10000):
        print(thread_name + ': ' + str(i))  # 输出 线程1:0~9999 或 线程2:0~9999

t1 = threading.Thread(target=run_thread, args=('线程1',))  # 创建线程
t2 = threading.Thread(target=run_thread, args=('线程2',))  # 创建另一个线程

t1.start() # 开始执行
t2.start() # 开始执行

t1.join() # 等待该线程结束后再继续往下运行
t2.join() # 等待该线程结束后再继续往下运行

而得到的结果(截取其中一段)为:

...
线程2: 350
线程1: 651
线程2: 351
线程1: 652
线程2: 352
线程1: 653
线程2: 353
线程1: 654
线程2: 354
...

发现 线程1线程2 交替输出,这意味着在异步执行。

分析
线程1: 运行着函数 run_thread
线程2: 运行着函数 run_thread
线程1 和 线程2 同时在运行,交替输出。

而正常情况下(同一线程内),程序的执行是同步的,只有一个函数执行完毕后,另一个函数才能执行。

结论:多线程每条线程上都能运行 Python 代码、处理 Python 写出的逻辑,即各线程之间是异步的。

  • 单线程异步

有人会说,JS是单线程的,但是也能进行异步操作啊,例如 setTimeout 定时器、ES6的 Promise 等。
Promise 来举例子:

let promise = new Promise((resolve, reject) => {

    setTimeout(() => {
        resolve()
    }, 5000)

    for (let i = 0; i < 10000; i++) {
        console.log('异步:' + i)
    }
})
// ------------------------------------------------------

// 开始异步
promise.then(function (value) { console.log('异步结束') })

// 同步代码
for (let i = 0; i < 1000; i++) {
    console.log('主线程:' + i)
}

我们期望 javascript 会异步执行 Promise 中的内容,像 Python 处理多线程一样,如下:

...
主线程: 350
异步: 651
主线程: 351
异步: 652
主线程: 352
异步: 653
主线程: 353
异步: 654
主线程: 354
...
异步结束

但事实结果是下面这样的

异步: 0
异步: 1
...
异步: 9998
异步: 9999
主线程: 0
主线程: 1
...
主线程: 9998
主线程: 9999
异步结束

Promise 中的 JS 代码,不是异步而是同步阻塞的。因为 Promise 只是一个 callback 的语法糖。
Promise 中真正作异步处理的,是 ajaxI/O、定时器等,Promise 只让回调写法更加优雅。

那么 ajaxI/O、定时器是异步的,单线程的 JS 是怎么处理的呢?
实际上, ajaxI/O、定时器,并不运行在 JS 的执行线程上。这些都是浏览器提供的 api,运行在浏览器的另外线程上,JS 无法进入除自己本身单线程外的其他线程,但能通知其余线程做相应操作。可以如下理解:

JS 线程:所有 JS 代码都在这执行,包括异步后的回调函数。该线程内是同步、阻塞的。
ajax 线程:对 ajax 的浏览器底层实现。
I/O 线程:对 I/O 的浏览器底层实现。
定时器线程:对定时器的浏览器底层实现。

所以:

结论: JS 是单线程,其异步实现是浏览器提供的(可以理解为这些实现用其他语言写的)。
重点:本身并不能做到执行一段 JS 代码的同时也执行另一段 JS 代码。肯定是阻塞的。

  • 多线程

再次用 Python 举例,虽然有多个线程同时运行,但是每个线程他们的全局变量是一样的、共用的。
所以,当其中一条线程读写某全局数据 A 时,别的线程也读写这个全局 A,很容易就会出现问题。(更详细的原因与CPU处理多线程机制有关,会在别的文章讨论)
这时候,要给数据 A 加锁,使 A 一次只能被一个线程使用,用完了别的线程才能用。

  • 单线程 JS

没法在执行一段 JS 代码的时候也执行另一段 JS 代码,单线程 JS 是同步的,不需要锁,所以没有锁这种东西。

猜你喜欢

转载自blog.csdn.net/aq115aq/article/details/80366388