vv13
博客订阅

理解javascript线程

进程和线程

  • 进程:系统资源分配与组织的最小单位,具备独立功能的程序即可成为一个进程,它有自己的内存空间
  • 线程:cpu调度和分派(运行)的最小单位,线程是一个进程的实体,可以共享进程独占的资源,自己只拥有一点在运行中必不可少的资源(程序计数器、寄存器、栈)

线程的作用:

  1. 大多数程序需要多个线程同步或互斥的并行完成工作,将工作分化到线程中简化了编程模型
  2. 线程很轻量,创建与销毁消耗的资源小
  3. 线程提高了cpu利用率,避免了如等待用户输入、异步资源请求等一系列阻塞操作

根据以上概念,多核cpu可以同时运行多个线程,那可以运行多个进程吗?普遍的答案是cpu只能同时运行一个线程,它是靠时间片轮转来实现的伪多进程,以下有一个关于利用多核心cpu的一个解释:

最早 UNIX 的调度是以 “进程” 为最小调度单位,那个时候还没有线程的概念。线程有两种,一种是 “用户态线程” ,对内核不可见,内核不可以调度,现在一般叫做纤程或协程。另一种是 “内核态线程”,由内核调度,也称作轻量进程 LWP 。现在说的线程,一般不特殊指定,都是内核线程。

能不能利用多核的关键是能不能被内核调度,既然内核态线程可以被调度,自然可以利用多核。

另外只要资源足够(内存) CPU 可以 hold 住任意多的进程或线程,这与 CPU 的核数无关。你在这里指的应该是 “运行” 。

javascript单线程机制

javascipt语言最大的特点就是单线程,同一时间只有一段代码在执行,这种语言有一个共同的特点:基于事件驱动。它虽然是单线程的,但是所在的平台肯定是异构的:

  1. 在浏览器中,界面渲染线程负责渲染、浏览器事件处理器、异步请求线程等,他们会配合javascript实现各种异步操作。
  2. 再node种,libuv库负责node api的执行,它将不同的任务分配给不同的线程,形成一个event loop,以异步的方式将任务的执行结果返回给V8引擎。

所以说,javascript执行是单线程的,方法是异步的,应用是多线程的。

任务队列

javascript核心为单线程机制,所有任务执行都需要按照顺序进行排队,如果前一个任务耗时长,后一个任务就需要等待,因此那些耗时较长的任务就被javascript的作者设计成了异步任务,任务队列正是存放异步任务的地方。

同步任务指在主线程中执行的任务栈,它会形成一个执行栈,像堆积木一样存放,实现层层调用,若有一个任务失败,则整个程序就会失败。异步任务就放到了任务队列里,等到异步任务成功响应(io输入读取完成、异步请求等待到服务器的响应),任务队列会通知主线程执行预先设定好的回调函数。

任务队列还有时间的概念,通过定时器setTimeout()和setInterval()这两个函数就可以在任务队列中规定多少事件后执行,它们调用后会返回定时器id,通过clearTimeout与clearInterval可以取消。

关于定时器的用法,常见的如setTimeout(fn, 0);,需要注意的是它不会立即执行,而是需要等到主进行这一次所有同步任务执行结束后,才会去读取任务队列的通知,执行相应的定时器方法。

事件循环(Event Loop)

Event Loop是一个程序结构,用于等待和发送消息事件,它是javascript单线程的重要处理机制,简单说就是将其他线程的资源取回主线程,执行事先设定的回调函数。

主线程会不断的从消息队列中读取事件,这个过程是不断循环的,因此称之为事件循环,事件循环经常用以下方式实现:

while (queue.waitForMessage()) {
    queue.processNextMessage()
}

而消息队列是一个先进先出的队列,当有异步操作完成或事件点击产生时,回调函数会作为消息进入到消息队列,等待主线程的读取与执行。

Web Worker

随着页面越来越复杂,html5制定了新的标准,而web worker的到来就是为浏览器端提供了多线程的编程能力,允许主线程将一些高计算或高延迟的工作分配给子线程。

Web Worker所执行的代码是在另一个作用域中,与当前代码不共享作用域,也无法访问dom,而且在父子线程中通讯时,传值是被序列化后复制到Worker中的,而不是直接引用传递,这样足以避免子线程影响父线程。