大前端开发中的线程进程问题

前端开发中的进程线程问题(摘录)


操作系统

在计算机刚刚起步的阶段,计算机硬件通常是昂贵的。所以,为了充分发掘计算机的存储、运算能力,或者邪恶点说是榨干计算机的硬件资源,使得计算机在尽可能段的时间内处理更多的事情。

交给计算机的任务,大致可以分为两类:I/O 密集型任务和 CPU 密集型任务。顾名思义,CPU 密集型任务,在执行过程中,需要大量的 CPU 资源。对于这种任务,我们可以大胆地将 CPU 资源交给它来调用——反正总是要占用 CPU 资源的。大体上,涉及到磁盘 I/O、网络存取的任务,就都是 I/O 密集型任务;此类任务往往不需要太多 CPU 资源,对于 CPU 来说,大多数时间被空耗在等待 I/O 完成上了。当人们认识到交给计算机的任务可以分为这两类的时候,人们就开始考虑如何做 CPU 的任务调度。在任务调度上,人们经历了多道程序、分时系统与多任务系统等阶段。

在多任务系统中,操作系统接管了所有硬件资源并持有对硬件控制的最高权限。在操作系统中执行的程序,都以进程的方式运行在更低的权限中。所有的硬件资源,由操作系统根据进程的优先级以及进程的运行状况进行统一的调配。

进程

android中的进程以及生命周期

An unusual and fundamental feature of Android is that an application process’s lifetime is not directly controlled by the application itself. Instead, it is determined by the system through a combination of the parts of the application that the system knows are running, how important these things are to the user, and how much overall memory is available in the system.

android进程优先级

  1. foreground process
  2. visible process
  3. service process
  4. cached process

    进程间通信
    多进程的问题:

    1.静态成员和单例失效:每个进程保持各自的静态成员和单例,相互独立。

    2.线程同步机制失效:每个进程有自己的线程锁。

    3.SharedPreferences可靠性下降:不支持并发写,会出现脏数据。

    4.Application多次创建:不同进程跑在不同虚拟机,每个虚拟机启动会创建自己的Application,自定义Application时生命周期会混乱。

多线程的优点

  • 将等待 I/O 操作的时间,调度到其他线程执行,提高 CPU 利用率
  • 将计算密集型的操作留给工作线程,预留线程保持与用户的交互
  • 在多 CPU/多核计算机下,有效吃干计算能力;
  • 相比多进程的程序,更有效地进行数据共享(在同一个进程空间)

协程

协程的特点在于是一个线程执行,那和多线程比,协程有何优势?

最大的优势就是协程极高的执行效率。因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显。

第二大优势就是不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。

因为协程是一个线程执行,那怎么利用多核CPU呢?最简单的方法是多进程+协程,既充分利用多核,又充分发挥协程的高效率,可获得极高的性能。

Python对协程的支持还非常有限,用在generator中的yield可以一定程度上实现协程。虽然支持不完全,但已经可以发挥相当大的威力了。

来看例子:

传统的生产者-消费者模型是一个线程写消息,一个线程取消息,通过锁机制控制队列和等待,但一不小心就可能死锁。

如果改用协程,生产者生产消息后,直接通过yield跳转到消费者开始执行,待消费者执行完毕后,切换回生产者继续生产,效率极高:

import time

def consumer():
    r = ''
    while True:
        n = yield r
        if not n:
            return
        print('[CONSUMER] Consuming %s...' % n)
        time.sleep(1)
        r = '200 OK'

def produce(c):
    c.next()
    n = 0
    while n < 5:
        n = n + 1
        print('[PRODUCER] Producing %s...' % n)
        r = c.send(n)
        print('[PRODUCER] Consumer return: %s' % r)
    c.close()

if __name__=='__main__':
    c = consumer()
    produce(c)

注意到consumer函数是一个generator(生成器),把一个consumer传入produce后:

首先调用c.next()启动生成器;

然后,一旦生产了东西,通过c.send(n)切换到consumer执行;

consumer通过yield拿到消息,处理,又通过yield把结果传回;

produce拿到consumer处理的结果,继续生产下一条消息;

produce决定不生产了,通过c.close()关闭consumer,整个过程结束。

整个流程无锁,由一个线程执行,produce和consumer协作完成任务,所以称为“协程”,而非线程的抢占式多任务。

最后套用Donald Knuth的一句话总结协程的特点:

“子程序就是协程的一种特例。”

生成器

生成器是可以迭代的,但是你 只可以读取它一次 ,因为它并不把所有的值放在内存中,它是实时地生成数据:

mygenerator = (x*x for x in range(3))
for i in mygenerator :
… print(i)
0
1
4

看起来除了把 [] 换成 () 外没什么不同。但是,你不可以再次使用 for i inmygenerator , 因为生成器只能被迭代一次:先计算出0,然后继续计算1,然后计算4,一个跟一个的…
yield关键字

yield 是一个类似 return 的关键字,只是这个函数返回的是个生成器。

def createGenerator() :
… mylist = range(3)
… for i in mylist :
… yield i*i

mygenerator = createGenerator() # create a generator
print(mygenerator) # mygenerator is an object!

for i in mygenerator:
… print(i)
0
1
4

这个例子没什么用途,但是它让你知道,这个函数会返回一大批你只需要读一次的值.

为了精通 yield ,你必须要理解:当你调用这个函数的时候,函数内部的代码并不立马执行 ,这个函数只是返回一个生成器对象,这有点蹊跷不是吗。

那么,函数内的代码什么时候执行呢?当你使用for进行迭代的时候.

现在到了关键点了!

第一次迭代中你的函数会执行,从开始到达 yield 关键字,然后返回 yield 后的值作为第一次迭代的返回值. 然后,每次执行这个函数都会继续执行你在函数内部定义的那个循环的下一次,再返回那个值,直到没有可以返回的。

如果生成器内部没有定义 yield 关键字,那么这个生成器被认为成空的。这种情况可能因为是循环进行没了,或者是没有满足 if/else 条件。

Android中的线程

single thread mode(not thread-safe)
1. Do not block the UI thread
2. Do not access the Android UI toolkit from outside the UI thread

ios中

1 RAM ROM
  RAM:运行内存,不能掉电存储。ROM:存储性内存,可以掉电存储,例如内存卡、Flash。
由于RAM类型不具备掉电存储能力(即一掉电数据消失),所以app程序一般存放于ROM中。RAM的访问速度要远高于ROM,价格也要高。
2 App程序启动
App程序启动,系统会把开启的那个App程序从Flash或ROM里面拷贝到内存(RAM),然后从内存里面执行代码。
另一个原因是CPU不能直接从内存卡里面读取指令(需要Flash驱动等等)。
3 内存分区:
栈区(stack):存放的局部变量、先进后出、一旦出了作用域就会被销毁;函数跳转地址,现场保护等;
程序员不需要管理栈区变量的内存;
(栈区地址从高到低分配);
堆区(heap):堆区的内存分配使用的是alloc;
需要程序员管理内存;
ARC的内存的管理,是编译器再便宜的时候自动添加 retain、release、autorelease;
(堆区的地址是从低到高分配)
全局区/静态区(static):
包括两个部分:未初始化过 、初始化过;
也就是说,(全局区/静态区)在内存中是放在一起的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域;
eg:int a;未初始化的。int a = 10;已初始化的。
常量区:常量字符串就是放在这里;
代码区: 存放App代码;
4 注意事项
在iOS中,堆区的内存是应用程序共享的,堆中的内存分配是系统负责的;
系统使用一个链表来维护所有已经分配的内存空间(系统仅仅纪录,并不管理具体的内容);
变量使用结束后,需要释放内存,OC中是根据引用计数==0,就说明没有任何变量使用该空间,那么系统将直接收回;
当一个app启动后,代码区,常量区,全局区大小已固定,因此指向这些区的指针不会产生崩溃性的错误。而堆区和栈区是时时刻刻变化的(堆的创建销毁,栈的弹入弹出),所以当使用一个指针指向这两个区里面的内存时,一定要注意内存是否已经被释放,否则会产生程序崩溃(也即是野指针报错)。

ios中的多线程

主要由四种:NSThread、NSoperationQueue、NSobject、GCD

Web前端的进程线程介绍

浏览器多进程架构
跟现在的很多多线程浏览器不一样,Chrome浏览器使用多个进程来隔离不同的网页。因此在Chrome中打开一个网页相当于起了一个进程

那么Chrome为什么要使用多进程架构?
在浏览器刚被设计出来的时候,那时的网页非常的简单,每个网页的资源占有率是非常低的,因此一个进程处理多个网页时可行的。然后在今天,大量网页变得日益复杂。把所有网页都放进一个进程的浏览器面临在健壮性,响应速度,安全性方面的挑战。因为如果浏览器中的一个tab网页崩溃的话,将会导致其他被打开的网页应用。另外相对于线程,进程之间是不共享资源和地址空间的,所以不会存在太多的安全问题,而由于多个线程共享着相同的地址空间和资源,所以会存在线程之间有可能会恶意修改或者获取非授权数据等复杂的安全问题。

在了解这个知识点线,我们需要先说明下什么是浏览器内核。

浏览器内核
简单来说浏览器内核是通过取得页面内容、整理信息(应用CSS)、计算和组合最终输出可视化的图像结果,通常也被称为渲染引擎。从上面我们可以知道,Chrome浏览器为每个tab页面单独启用进程,因此每个tab网页都有由其独立的渲染引擎实例。

浏览器内核是多线程
浏览器内核是多线程,在内核控制下各线程相互配合以保持同步,一个浏览器通常由以下常驻线程组成:

  1. GUI 渲染线程
  2. JavaScript引擎线程
  3. 定时触发器线程
  4. 事件触发线程
  5. 异步http请求线程
  6. GUI渲染线程

GUI渲染线程负责渲染浏览器界面HTML元素,当界面需要重绘(Repaint)或由于某种操作引发回流(reflow)时,该线程就会执行。在Javascript引擎运行脚本期间,GUI渲染线程都是处于挂起状态的,也就是说被”冻结”了.

Javascript引擎线程
Javascript引擎,也可以称为JS内核,主要负责处理Javascript脚本程序,例如V8引擎。Javascript引擎线程理所当然是负责解析Javascript脚本,运行代码。

Javascript是单线程的
Javascript是单线程的, 那么为什么Javascript要是单线程的?

这是因为Javascript这门脚本语言诞生的使命所致:JavaScript为处理页面中用户的交互,以及操作DOM树、CSS样式树来给用户呈现一份动态而丰富的交互体验和服务器逻辑的交互处理。如果JavaScript是多线程的方式来操作这些UI DOM,则可能出现UI操作的冲突; 如果Javascript是多线程的话,在多线程的交互下,处于UI中的DOM节点就可能成为一个临界资源,假设存在两个线程同时操作一个DOM,一个负责修改一个负责删除,那么这个时候就需要浏览器来裁决如何生效哪个线程的执行结果。当然我们可以通过锁来解决上面的问题。但为了避免因为引入了锁而带来更大的复杂性,Javascript在最初就选择了单线程执行。

GUI 渲染线程 与 JavaScript引擎线程互斥!
由于JavaScript是可操纵DOM的,如果在修改这些元素属性同时渲染界面(即JavaScript线程和UI线程同时运行),那么渲染线程前后获得的元素数据就可能不一致了。因此为了防止渲染出现不可预期的结果,浏览器设置GUI渲染线程与JavaScript引擎为互斥的关系,当JavaScript引擎执行时GUI线程会被挂起,GUI更新会被保存在一个队列中等到引擎线程空闲时立即被执行。

JS阻塞页面加载
从上面我们可以推理出,由于GUI渲染线程与JavaScript执行线程是互斥的关系,当浏览器在执行JavaScript程序的时候,GUI渲染线程会被保存在一个队列中,直到JS程序执行完成,才会接着执行。因此如果JS执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞的感觉。

定时触发器线程
浏览器定时计数器并不是由JavaScript引擎计数的, 因为JavaScript引擎是单线程的, 如果处于阻塞线程状态就会影响记计时的准确, 因此通过单独线程来计时并触发定时是更为合理的方案。

事件触发线程
当一个事件被触发时该线程会把事件添加到待处理队列的队尾,等待JS引擎的处理。这些事件可以是当前执行的代码块如定时任务、也可来自浏览器内核的其他线程如鼠标点击、AJAX异步请求等,但由于JS的单线程关系所有这些事件都得排队等待JS引擎处理。

异步http请求线程
在XMLHttpRequest在连接后是通过浏览器新开一个线程请求, 将检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件放到 JavaScript引擎的处理队列中等待处理

参考链接IOS内存管理
浏览器进程?线程?傻傻分不清楚!
协程链接

  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值