# 浏览器工作原理
# CPU、进程、线程 的关系
- CPU(工厂):计算机的核心是CPU,承担所有计算任务。
- 进程(车间):进程是CPU资源分配和独立运行的最小单位。进程之间相互独立,任一时刻,CPU总是运行一个进程,其他进程处于非运行状态。
- CPU使用时间片轮转进度算法来实现同时运行多进程。
- 不同进程之间也可以通信,不过代价较大。
- 线程(工人):线程是CPU调度的最小单位。一个进程中可以有多个线程,多个线程共享进程资源。
- 单线程与多线程,都是指在一个进程内的单和多。
# 线程和进程的区别
- 进程是
资源分配的最小单位
,线程是CPU调度(即程序执行)的最小单位
; - 一个进程可以包含多个线程,每个进程有自己的独立内存空间,不同进程间数据很难共享;
- 同一进程下不同线程间共享全局变量、静态变量,数据很易共享;
- 进程要比线程消耗更多的计算机资源;
- 进程间不会相互影响,而一个线程挂掉将导致其他线程阻塞。
# 进程间通信(6种)
管道pipe
:速度慢,容量有限,半双工(数据只能单向流动),且只有父子进程能通讯命名管道namedpipe
:同样速度慢,半双工,但是相比pipe,具有任何进程间都能通讯的特点消息队列MessageQueue
:容量受到系统限制,且要注意第一次读的时候,要考虑上一次没有读完数据的问题信号量Semaphore
:是一个计数器,不能传递复杂消息,只可用来控制多个进程对共享资源的访问,起同步作用共享内存SharedMemory
:能够很容易控制容量,速度快,但要保持同步,比如一个进程在写的时候,另一个进程要注意读写的问题,相当于线程中的线程安全。当然,共享内存区同样可以用作线程间通讯,不过没这个必要,线程间本来就已经共享了同一进程内的一块内存套接字Socket
:与其他通信机制不同的是,它可用于不同及其间的进程通信。
# 线程间通信(3种)
- 锁机制:包括
互斥锁
、条件变量
、读写锁
- a.
互斥锁
提供了以排他方式防止数据结构被并发修改的方法。 - b.
条件变量
对条件的测试是在互斥锁
的保护下进行的,始终与互斥锁
一起使用。可以以原子的方式阻塞进程,直到某个特定条件为真为止。 - c.
读写锁
允许多个线程同时读共享数据,而对写操作是互斥的。
- a.
- 信号量机制(Semaphore):包括无名线程信号量和命名线程信号量
- 信号机制(Signal):类似进程间的信号处理线程间的通信目的.主要是用于线程同步,所以线程没有像进程通信中的用于数据交换的通信机制。
多线程中产生死锁的原因和解决死锁的办法
- 线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行。
- 产生死锁的原因,概括来说是由于线程间竞争系统资源,或者进程的推进顺序不当引起的。
- 具体来说,如果在一个系统中以下四个条件同时成立,那么就能引起死锁(
4个必要条件
):- 互斥条件:进程要求对所分配的资源进行排它性控制,即在一段时间内某资源仅为一进程所占用。
- 请求和保持条件:当进程因请求资源而阻塞时,对已获得的资源保持不放。
- 不剥夺条件:进程已获得的资源在未使用完之前,不能剥夺,只能在使用完时由自己释放。
- 环路等待条件:在发生死锁时,必然存在一个进程--资源的环形链。
- 具体来说,如果在一个系统中以下四个条件同时成立,那么就能引起死锁(
- 解决死锁的办法:
加锁顺序
(线程按照一定的顺序加锁)加锁时限
(线程尝试获取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,并释放自己占有的锁)死锁检测
# 并行与并发
- 并行:两个任务同时运行(多个CPU)。
- 并发:指两个任务同时请求运行,处理器一次只能接受一个任务,就会把两个任务安排
轮流执行
,由于CPU时间片运行时间很短,就会感觉两个任务同时执行。
# 浏览器是多进程的
- 计算机中的每个应用程序都是一个进程,应用程序可分为多个子模块,这些子模块就对应多个子进程。
- 浏览器应用程序就是一个大进程,而每个tab页都是一个子进程,所以浏览器是多进程的。
# 浏览器多进程分类
- 主进程:浏览器的主进程(负责协调、主控),只有一个;
- 第三方插件进程:每种类型的插件对应一个进程,仅当使用该插件时才创建;
- GPU进程:用于3D绘制,最多一个;
渲染进程(浏览器内核)
:- 每个tab页面是一个渲染进程,互不影响。
- 这个渲染进程也是多线程的,包含5大类线程。
# 渲染进程是多线程的
每一个tab页面可以看作是「渲染进程」进程,然后这个进程是多线程的,它有几大类线程:
GUI渲染线程
- 负责渲染页面,布局和绘制
- 页面需要重绘和回流时,该线程就会执行
- 与js引擎线程互斥,防止渲染结果不可预期
JS引擎线程
- 负责处理解析和执行javascript脚本程序
- 只有一个JS引擎线程(单线程)
- 与GUI渲染线程互斥,防止渲染结果不可预期
事件触发线程
- 用来控制事件循环(鼠标点击、setTimeout、ajax等)
- 当事件满足触发条件时,将事件放入到JS引擎所在的执行队列中
定时器线程
- setInterval与setTimeout所在的线程
- 定时任务并不是由JS引擎计时的,是由定时器线程来计时的
- 计时完毕后,通知事件触发线程
异步http请求线程
- 浏览器有一个单独的线程用于处理AJAX请求
- 当请求完成时,若有回调函数,通知事件触发线程
# 两个问题
# 为什么 javascript 是单线程的
- 创建 js 语言时,多线程架构的硬件支持并不好。
- 因为多线程的复杂性,多线程操作需要加锁,编码的复杂性会增高。
- 作为浏览器脚本语言,js的主要用途是与用户互动,以及操作DOM,如果同时操作 DOM ,在多线程不加锁的情况下,最终会导致 DOM 渲染的结果不可预期。
- 为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质。
# 为什么 GUI渲染线程 与 JS引擎线程 互斥
由于 js 可以操作 DOM,如果同时修改元素属性并同时渲染界面(即 JS线程和GUI线程同时运行),会导致渲染线程前后获得的元素可能不一致。因此,为了防止渲染出现不可预期的结果,浏览器设定 GUI渲染线程和JS引擎线程为互斥关系,当JS引擎线程执行时GUI渲染线程会被挂起,GUI更新则会被保存在一个队列中等待JS引擎线程空闲时立即被执行。
这部分建议与任务队列 与 Event Loop一同食用。
# 浏览器存储
# Cookie 和 Session
- Cookie 和 Session都为了用来保存状态信息,都是保存客户端状态的机制,它们都是
为了解决HTTP无状态
的问题的。 - Cookie是保存在客户端浏览器的一小段的文本信息,
每个domain最多只能有20条Cookie
,每个Cookie长度不能超过4KB
,否则会被截掉。考虑到安全应当使用Session而不是cookie。 - Session保存在服务器上。Session的运行依赖Session Id,而Session Id是存在Cookie中的,也就是说,如果浏览器禁用了Cookie,同时Session也会失效(但是可以通过其它方式实现,比如在url中传递session_id)。如果Cookie被人拦截了,那人就可以取得所有的session信息。即使加密也与事无补,因为拦截者并不需要知道Cookie的意义,他只要原样转发Cookie就可以达到目的了。考虑到减轻服务器性能方面,应当使用cookie而不是session。
- 注:
- cookie默认是不能跨域访问的,但对于主域相同子域不同的两个页面,document.domain设置为相同的父域名,即可实现不同子域名之间的跨域通信。
- cookie的键/值对中的值不允许出现分号、逗号和空白符,因此在设置cookie前要用encodeURIComponent()编码,读取时再用decodeURIComponent()解码;
- cookie默认的有效期是浏览器会话期间,作用域是整个浏览器而不仅仅局限于窗口或标签页;
# Cookie属性设置
cookie选项包括:expires、domain、path、secure、HttpOnly。在设置这些属性时,属性之间由一个分号和一个空格隔开:
- expires:cookie失效日期。
- domain、path:域名、路径,两者加起来就构成了 URL,domain和path一起来限制 cookie 能被哪些 URL 访问。
- secure:用来设置cookie只在确保安全的请求中才会发送。当请求是HTTPS或者其他安全协议时,包含 secure 选项的 cookie才能被发送至服务器。
- HttpOnly:用来设置cookie是否能通过 js 去访问。默认情况下不带httpOnly选项,即可以访问。
- SameSite:用来防止 CSRF 攻击和用户追踪。
服务端设置 cookie:
- 服务端都会返回response。header中有一项叫set-cookie,是服务端专门用来设置cookie的。
- 一个set-cookie字段只能设置一个cookie,当你要想设置多个 cookie,需要添加同样多的set-Cookie字段。
- 服务端可以设置cookie 的所有选项:expires、domain、path、secure、HttpOnly
客户端设置 cookie:
- document.cookie="age=12; expires=Thu, 26 Feb 2116 11:50:25 GMT; domain=sankuai.com; path=/";
# Cookie 与 Web Storage
H5的Web Storage提供了两个拥有一致API的对象:
- window.localStorage:用来在本地存储永久数据的;
- window.sessionStorage:用来存储会话数据的(也就是tab不关闭时的数据);
- 这两个对象存储的都是 name/value 。而且与Cookie不同,Web存储的大小比Cookie的大多了(
最少5MB
),而且通过这个策略存储的数据是永远不会传输至server
的。 - 纵使Cookie的大小是受限的,并且每次你请求一个新的页面的时候Cookie都会被发送过去,这样无形中浪费了带宽,另外Cookie不可以跨域调用。但是Cookie也是不可以或缺的:Cookie的作用是与服务器进行交互,作为HTTP规范的一部分而存在 ,而Web Storage仅仅是为了在本地“存储”数据而生。
- 除此之外,Web Storage拥有setItem,getItem,removeItem,clear等方法,不像Cookie需要前端开发者自己封装setCookie,getCookie。
# 登录验证方案
# 基于cookie/session的身份验证
基于cookie/session
的验证是有状态的
,即 验证信息必须同时在客户端和服务端保存。这个信息服务端一般在数据库中记录,而前端会保存在cookie中。
验证的一般流程如下:
- 用户输入登陆凭据;
- 服务器验证凭据是否正确,并创建会话,然后把会话数据存储在数据库session中;
- 具有会话sessionId的cookie被放置在用户浏览器中;
- 在后续请求中,服务器会根据数据库验证sessionId,如果验证通过,则继续处理;
- 一旦用户登出,服务端和客户端同时销毁该会话。
# 基于token的身份验证
基于token
的验证是无状态的
。服务器不记录哪些用户已登陆或者已经发布了哪些JWT(JSON Web Tokens)。对服务器的每个请求都需要带上验证请求的token。该标记既可以加在header中,可以在POST请求的主体中发送,也可以作为查询参数发送。
验证流程如下:
- 用户输入登陆凭据;
- 服务器验证凭据是否正确,然后返回一个经过签名的token;
- 客户端负责存储token,可以存在local storage,或者cookie中;
- 对服务器的请求带上这个token;
- 服务器对JWT进行解码,如果token有效,则处理该请求;
- 一旦用户登出,客户端销毁token。
# token相对cookie的优势
无状态
:基于token的验证是无状态的,这也许是它相对cookie来说最大的优点。后端服务不需要记录token。每个令牌都是独立的,包括检查其有效性所需的所有数据,并通过声明传达用户信息。服务器唯一的工作就是在成功的登陆请求上签署token,并验证传入的token是否有效。防跨站请求伪造(CSRF)
:使用了token作为验证手段时,攻击者无法获取正确的token,所以可避免CSRF。性能
:一次网络往返时间(通过数据库查询session信息)总比做一次 HMAC-SHA256 计算的Token验证和解析要费时得多。多站点使用
:cookie绑定到单个域。而使用token就没有这样的问题。这对于需要向多个服务获取授权的单页面应用程序尤其有用。支持移动平台
:在移动平台上,cookie是不被支持的,而token可以同时支持浏览器,iOS和Android等移动平台。
← 关于axios的使用 浏览器渲染机制 →