javascript之单线程

javascript的单线程机制


为什么javascript采用单线程机制?

        先说明一下线程和进程的不同:进程是执行的应用程序,每个进程都是由私有的虚拟地址空间、代码、数据和其它系统资源所组成,进程在运行过程中能够申请创建和使用系统资源,这些资源也会随着进程的终止而被销毁。线程是进程内的一个独立执行单元,在不同的线程之间是可以共享进程资源的,所以在多线程的情况下,需要特别注意对临界资源的访问控制。在系统创建进程之后就开始启动执行进程的主线程,而进程的生命周期和这个主线程的生命周期一致,主线程的退出也就意味着进程的终止和销毁。主线程有系统进程所创建,同时用户也可以自己创建其他线程,这一系列的线程都会并发地运行与同一个进程中。


        多线程的并发,能够提高CPU利用率,提升应用程序性能。但是javascript中却没有使用多线程,而是使用了单线程。而其中的原因,要从Javascript的用途说起,javascript是一种浏览器脚本语言,主要实现与用户的交互,操作DOM。如果使用多线程的方式操作DOM,则可能出现操作的冲突。假设有两个线程同时操作一个DOM元素,线程1要求浏览器删除DOM,线程2要求修改DOM样式,浏览器是无法决定采用哪个线程的操作的。为了简化开发,javascript支持的是单线程。

        

        单线程运行,即在某一时刻内只能执行特定的一个任务,只有当这个任务完全执行完毕后,才能执行下一个任务。即会发生阻塞的现象。比如如果有I/O输入的情况,而I/O输入过程比较耗时,在单线程中,那么程序会阻塞在I/O输入模块上,只有输入完成后,才会执行之后的程序。这样就会造成CPU很长一段时间都是空闲的。所以需要引入异步实现任务的功能。而javascript具有回调的特性(注意,回调和单线程并不是一回事,不要混淆,单线程只是很好地应用了回调这个特性),对于比较耗时的I/O输入,不需要等待输入的完成再执行之后的代码,而是可以先执行后面的代码,等这些耗时的任务完成后则以回调的方式执行相应的处理,这个过程很完美地体现了回调机制。


Runtime概念


下面给出的是一个理论上的模型,现在js引擎已着重实现和优化了以下所描述的几个概念



Stack(栈)

这里放着JavaScript正在执行的任务。每个任务被称为帧(stack of frames)。

function f(b){
  var a = 12;
  return a+b+35;
}

function g(x){
  var m = 4;
  return f(m*x);
}

g(21);
上述代码调用 g 时,创建栈的第一帧,该帧包含了 g 的参数和局部变量。当 g 调用 f 时,第二帧就会被创建,并且置于第一帧之上,当然,该帧也包含了 f 的参数和局部变量。当 f 返回时,其对应的帧就会出栈。同理,当 g 返回时,栈就为空了(栈的特定就是后进先出 Last-in first-out (LIFO))。

Heap(堆)

一个用来表示内存中一大片非结构化区域的名字,对象都被分配在这。

Queue(队列)

一个 JavaScript runtime 包含了一个任务队列,该队列是由一系列待处理的任务组成。而每个任务都有相对应的函数。当栈为空时,就会从任务队列中取出一个任务,并处理之。该处理会调用与该任务相关联的一系列函数(因此会创建一个初始栈帧)。当该任务处理完毕后,栈就会再次为空。(Queue的特点是先进先出 First-in First-out (FIFO))。

为了方便描述与理解,作出以下约定:

  • Stack栈为主线程
  • Queue队列为任务队列(等待调度到主线程执行)

那么,现在具体讲一下主线程和任务队列


主线程和任务队列


        单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。 

        如果排队是因为计算量大,CPU忙不过来,倒也算了,但是很多时候CPU是闲着的,因为IO设备(输入输出设备)很慢(比如Ajax操作从网络读取数据),不得不等着结果出来,再往下执行。


        JavaScript语言的设计者意识到,这时主线程完全可以不管IO设备,挂起处于等待中的任务,先运行排在后面的任务。等到IO设备返回了结果,再回过头,把挂起的任务继续执行下去。 
于是,所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。 
        具体来说,异步执行的运行机制如下。(同步执行也是如此,因为它可以被视为没有异步任务的异步执行。) 
(1)所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。 
(2)主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。 
(3)一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。 
(4)主线程不断重复上面的第三步。 

事件和回调函数 


        "任务队列"是一个事件的队列(也可以理解成消息的队列),IO设备完成一项任务,就在"任务队列"中添加一个事件,表示相关的异步任务可以进入"执行栈"了。主线程读取"任务队列",就是读取里面有哪些事件。 
"任务队列"中的事件,除了IO设备的事件以外,还包括一些用户产生的事件(比如鼠标点击、页面滚动等等)。只要指定过回调函数,这些事件发生时就会进入"任务队列",等待主线程读取。 
所谓"回调函数"(callback),就是那些会被主线程挂起来的代码。异步任务必须指定回调函数,当主线程开始执行异步任务,就是执行对应的回调函数。 

        "任务队列"是一个先进先出的数据结构,排在前面的事件,优先被主线程读取。主线程的读取过程基本上是自动的,只要执行栈一清空,"任务队列"上第一位的事件就自动进入主线程。但是,由于存在后文提到的"定时器"功能,主线程首先要检查一下执行时间,某些事件只有到了规定的时间,


Event Loop机制

        之所以被称为Event loop,是因为它以以下类似方式实现:

while(queue.waitForMessage()){
  queue.processNextMessage();
}
        正如上述所说,“任务队列”是一个事件的队列,如果I/O设备完成任务或用户触发事件(该事件指定了回调函数),那么相关事件处理函数就会进入“任务队列”,当主线程空闲时,就会调度“任务队列”里第一个待处理任务,(FIFO)。当然,对于定时器,当到达其指定时间时,才会把相应任务插到“任务队列”尾部。

        上图主线程的绿色部分,还是表示运行时间,而橙色部分表示空闲时间。每当遇到I/O的时候,主线程就让Event Loop线程去通知相应的I/O程序,然后接着往后运行,所以不存在红色的等待时间。等到I/O程序完成操作,Event Loop线程再把结果返回主线程。主线程就调用事先设定的回调函数,完成整个任务。


定时器问题

        在任务队列中,也可以存放定时器。在js中,定时器函数有两个setTimeout()和setInterval()。

        setTimeout()方法在指定的时间过后执行代码,需要传递两个参数,第一个参数是一个函数(函数代码块或者是函数名),第二个参数是指等待的毫秒数。即在第二个参数指定的时间后执行第一个指定的函数内容。但是这里要注意的是,javascript是一个单线程程序的解释器,定时器是被当做异步任务来处理的,所以setTimeout()的第二个参数告诉javascript再过多长时间把当前任务(第一个参数指定的任务)添加到队列中即使是setTimeout中第二个参数设置为0,也不会立马执行)。只有等到主线程的任务执行完,并且任务对列是空的,才会执行setTimeout指定的代码任务。

        setTimeout()方法,在有些时候并不会按照我们设置的超时时间来执行指定代码,但是通过调用setTimeout方法,改变了代码流程,将主线程中的代码执行完毕,才会执行指定的代码。

setInterval()方法,每隔指定的时间就执行一次代码。需要传递两个参数,第一个参数为要执行的代码,第二个参数为每次执行前需要等待的毫秒数。


参考资料

关于javascript单线程的一些事

单线程运行机制及setTimeout(fn,0)

单线程模型

Javascript是单线程的深入分析




  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值